Aprofundamento em Flexbox
O cavalo de batalha do layout mobile - direção, flex, align e receitas comuns.
Busque em todas as páginas da documentação
O cavalo de batalha do layout mobile - direção, flex, align e receitas comuns.
Cartão de receita de referência rápida - pronto para copiar e colar.
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>O conteúdo rolável ou a lista vai aqui.</Text>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>3 não lidas</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 }, // cresce - empurra o footer para baixo
footer: {
padding: 16,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#e2e8f0",
},
footerText: { textAlign: "center", color: "#64748b" },
});Quando usar isso: Qualquer layout de tela em React Native - linhas, colunas, barras de ferramentas, cartões e a clássica estrutura header/content/footer rodam no flexbox do 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 do flexbox", done: false },
{ id: "2", title: "Lançar tokens de modo escuro", done: true },
{ id: "3", title: "Testar layout dobrável", done: false },
];
export default function TaskBoard() {
return (
<View style={styles.screen}>
{/* Barra de ferramentas (row) */}
<View style={styles.toolbar}>
<Text style={styles.heading}>Tarefas</Text>
<View style={styles.badge}>
<Text style={styles.badgeText}>{TASKS.filter((t) => !t.done).length}</Text>
</View>
</View>
{/* Filho flexível rolável */}
<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 ? "Concluída" : "Aberta"}</Text>
</View>
))}
</ScrollView>
{/* Barra de ação inferior */}
<View style={styles.actionBar}>
<View style={styles.actionPrimary}>
<Text style={styles.actionPrimaryText}>Adicionar tarefa</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 },
});O que isso demonstra:
flex: 1 na raiz da tela para que o layout preencha a viewport do dispositivo.flexDirection: "row" para a barra de ferramentas e linhas da lista - explícito, nunca assumido.justifyContent: "space-between" para fixar o título à esquerda e o badge à direita.flex: 1 no Text do título para que ele seja truncado enquanto os metadados permanecem visíveis.flexShrink: 0 no ponto e nos metadados para que os ícones não sejam achatados.gap para espaçamento consistente entre os filhos da linha sem cálculos de margem.ScrollView como um filho flexível que rola enquanto a barra de ação permanece fixada.View é um contêiner flexível. O display padrão é flex; não há divisão block/inline como na web.flexDirection padrão é column - os filhos se empilham verticalmente, a menos que você defina row.alignItems padrão é stretch - os filhos se expandem para a largura do eixo transversal do contêiner (em uma coluna, isso significa largura total).flexShrink padrão é 0 nos filhos (diferente de muitos padrões do navegador) - os itens mantêm seu tamanho intrínseco, a menos que você permita o encolhimento.flex: 1 é um atalho para flexGrow: 1, flexShrink: 1, flexBasis: 0 - o filho reivindica todo o espaço restante ao longo do eixo principal.flexDirection | Eixo principal (justifyContent) | Eixo transversal (alignItems) |
|---|---|---|
column (padrão) | Vertical ↕ | Horizontal ↔ |
row | Horizontal ↔ | Vertical ↕ |
column-reverse | Vertical (invertido) | Horizontal |
row-reverse | Horizontal (invertido) | Vertical |
// Coluna (padrão): justifyContent = vertical, alignItems = horizontal
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Centralizado</Text>
</View>
// Linha: justifyContent = horizontal, alignItems = vertical
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
<Icon />
<Text style={{ flex: 1 }}>Label preenche espaço do meio</Text>
<Chevron />
</View>| Propriedade | Efeito |
|---|---|
flex: 1 | Cresce para preencher o espaço restante; encolhe se necessário |
flexGrow: 1 | Ocupa espaço extra quando os irmãos não o fazem |
flexShrink: 1 | Permite encolhimento abaixo do tamanho intrínseco quando lotado |
flexShrink: 0 | Nunca encolhe - protege ícones, badges, avatares |
flexBasis: "auto" | Começa pelo tamanho do conteúdo, depois cresce/encolhe |
flexBasis: 0 | Ignora o tamanho do conteúdo ao distribuir espaço |
alignSelf | Substitui o alignItems do pai para um único filho |
1. Cabeçalho / conteúdo rolável / rodapé
<View style={{ flex: 1 }}>
<Header />
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }} />
<Footer />
</View>2. Ações de barra de ferramentas espaçadas horizontalmente
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<BackButton />
<Title />
<MenuButton />
</View>3. Segmentos de largura igual (abas)
<View style={{ flexDirection: "row" }}>
{tabs.map((tab) => (
<Pressable key={tab} style={{ flex: 1, alignItems: "center" }}>
<Text>{tab}</Text>
</Pressable>
))}
</View>4. Ações de cartão alinhadas na parte inferior
<View style={{ flex: 1, justifyContent: "flex-end", padding: 16 }}>
<PrimaryButton />
</View>5. Linha de chips que quebra
<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 cobre chaves específicas de flex; ViewStyle é o tipo completo de estilo de view.[styles.base, isActive && styles.active].false, null, undefined) são ignoradas com segurança.Assumir direção de linha - Colar trechos da web sem flexDirection: "row" empilha itens verticalmente. Correção: Defina flexDirection: "row" explicitamente para layouts horizontais.
Falta de flex: 1 na raiz da tela - O conteúdo não preenche a viewport; rodapés flutuam no meio da tela. Correção: Aplique flex: 1 à View mais externa (dentro do seu wrapper de área segura).
Usar height: "100%" em vez de flex: 1 - Altura percentual requer um pai com altura explícita; falha imprevisivelmente em árvores aninhadas. Correção: Prefira flex: 1 para layouts de preenchimento de espaço restante.
Texto truncado em linhas sem flex: 1 - Títulos longos empurram os irmãos para fora da tela em vez de truncar. Correção: Dê ao Text flex: 1 (e numberOfLines) dentro de uma linha; defina flexShrink: 0 em ícones finais.
justifyContent: "center" sem flex: 1 - O centralização só funciona dentro da altura do conteúdo do contêiner, não da tela inteira. Correção: Dê ao contêiner flex: 1 para que ele ocupe o espaço disponível.
alignItems: "center" encolhendo filhos da linha - Os filhos encolhem para a largura do conteúdo; um filho com width: "100%" pode não se esticar. Correção: Use alignSelf: "stretch" no filho que precisa de largura total, ou use alignItems: "stretch" no pai.
Padding em ScrollView em vez de contentContainerStyle - Indicadores de rolagem e bounce se comportam de forma estranha no iOS. Correção: Coloque padding e gap em contentContainerStyle; mantenha style={{ flex: 1 }} na própria ScrollView.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Yoga flexbox (View) | Padrão para todo layout RN - linhas, colunas, preencher espaço | Você precisa de sobreposições de posicionamento absoluto (use position: "absolute") |
FlatList / SectionList | Listas longas virtualizadas com linhas homogêneas | Conteúdo estático curto - ScrollView + map é mais simples |
react-native-reanimated layout animations | Animações de alterações flexíveis, layouts de entrada/saída | Telas estáticas sem requisitos de movimento |
Larguras percentuais (width: "50%") | Divisões simples de duas colunas | Grades de várias colunas precisas - calcule dp a partir de useWindowDimensions |
| CSS Grid (apenas para web) | Expo web com grade responsiva | Layouts de produção iOS/Android - flex é a escolha portátil |
column - os filhos se empilham de cima para baixo. Isso é o oposto de muitas barras de navegação web que fluem implicitamente em uma linha. Sempre defina flexDirection: "row" quando quiser um layout horizontal.
É um atalho para flexGrow: 1, flexShrink: 1, flexBasis: 0. O filho cresce para consumir todo o espaço restante no eixo principal do pai e pode encolher se o espaço for limitado. Coloque flex: 1 na raiz da tela e nas áreas de conteúdo entre cabeçalhos/rodapés fixos.
justifyContent - alinhamento ao longo do eixo principal (vertical em uma coluna, horizontal em uma linha).alignItems - alinhamento ao longo do eixo transversal (horizontal em uma coluna, vertical em uma linha).Centralizar um único filho em uma tela inteira: flex: 1, justifyContent: "center", alignItems: "center".
<View style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }} />
<Footer />
</View>A ScrollView com flex: 1 ocupa todo o espaço acima do rodapé. O rodapé é renderizado em sua altura natural abaixo.
numberOfLines precisa de uma largura limitada. Em uma linha, dê ao Text flex: 1 para que ele compita por espaço, e flexShrink: 0 em ícones finais para que eles mantenham seu tamanho.
Impede que um filho encolha abaixo de seu tamanho intrínseco quando a linha está lotada. Use-o em avatares, ícones, badges e botões de largura fixa para que não sejam achatados.
Sim - gap, rowGap e columnGap são suportados no React Native 0.71+. Eles adicionam espaço entre os filhos flexíveis sem margem nos primeiros/últimos itens. Funciona em View e contentContainerStyle do ScrollView.
justifyContent: "space-between" - o primeiro no início, o último no fim, espaço igual entre eles.justifyContent: "space-around" - espaço igual ao redor de cada item.justifyContent: "space-evenly" - lacunas perfeitamente uniformes, incluindo as bordas.Para barra de ferramentas com voltar/título/menu, space-between é o mais comum.
Sim - flex é recursivo. Uma tela de coluna pode conter barras de ferramentas de linha, itens de lista de linha e cartões de coluna. Cada View estabelece seu próprio contexto flexível com eixos principal/transversal independentes.
justifyContent: "center" centraliza dentro da própria altura do contêiner. Se o contêiner envolve seu conteúdo (sem flex: 1), a centralização não tem espaço extra para trabalhar. Adicione flex: 1 ao contêiner.
flexWrap: "wrap" permite que os filhos de linha fluam para a próxima linha quando excedem a largura do contêiner. Combine com gap para grades de chips e nuvens de tags. Defina larguras explícitas nos filhos ou use base percentual para colunas uniformes.
Substitui o alignItems do pai para um único filho. Exemplo: o pai tem alignItems: "center", mas um filho precisa de largura total - defina alignSelf: "stretch" nesse filho.
Prefira gap para espaçamento uniforme entre irmãos. Use margem quando um item precisar de espaçamento assimétrico (por exemplo, empurrar um botão para a direita com marginLeft: "auto" em uma linha - embora justifyContent: "space-between" seja mais limpo).
Dê à ScrollView style={{ flex: 1 }} para que ela preencha o espaço entre os irmãos. Coloque padding e gap em contentContainerStyle. Adicione contentContainerStyle={{ flexGrow: 1 }} quando precisar que o conteúdo de rolagem preencha a viewport (por exemplo, centralizar texto de estado vazio).
Flex distribui espaço dentro de um contêiner; breakpoints decidem qual árvore flex renderizar. Veja Layout Responsivo e Adaptativo e Dimensões e Layout Responsivo.
StyleSheet.create, estilos inline e o modelo mental de estilização do RNView e regras de aninhamento de textouseWindowDimensions para cálculos de largura de colunaVersõ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