StyleSheet e por que ele importa
No React Native, estilos são objetos JavaScript. Você pode escrever estilos inline, mas para apps reais é melhor centralizar e padronizar com StyleSheet.create, que ajuda na organização, reduz recriações de objetos e melhora a legibilidade.
StyleSheet.create na prática
Crie um arquivo de estilos (ou um módulo de tema) e exporte um objeto com nomes claros:
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
screen: {
flex: 1,
padding: 16,
backgroundColor: '#0B1220',
},
title: {
fontSize: 20,
fontWeight: '700',
color: '#FFFFFF',
},
subtitle: {
marginTop: 8,
fontSize: 14,
color: '#B8C0CC',
},
});Use no componente:
import { View, Text } from 'react-native';
import { styles } from './styles';
export function ProfileHeader() {
return (
<View style={styles.screen}>
<Text style={styles.title}>Olá, Ana</Text>
<Text style={styles.subtitle}>Bem-vinda de volta</Text>
</View>
);
}Flexbox no React Native: layout na prática
O React Native usa flexbox para layout. Por padrão, flexDirection é 'column' (diferente do CSS web, que costuma ser row em muitos exemplos). Os conceitos principais:
- Eixo principal: definido por
flexDirection(rowoucolumn). - Eixo cruzado: perpendicular ao principal.
- justifyContent: alinha itens no eixo principal.
- alignItems: alinha itens no eixo cruzado.
- alignSelf: sobrescreve o alinhamento de um item específico no eixo cruzado.
- flex: controla quanto um item cresce/encolhe para ocupar espaço.
Exemplo 1: entendendo eixos e alinhamento
Imagine um “cartão” com avatar à esquerda e textos à direita (layout em linha):
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
import { StyleSheet, View, Text, Image } from 'react-native';
export function UserRow() {
return (
<View style={s.card}>
<Image
source={{ uri: 'https://picsum.photos/120' }}
style={s.avatar}
/>
<View style={s.info}>
<Text style={s.name}>Ana Souza</Text>
<Text style={s.role}>Product Designer</Text>
</View>
</View>
);
}
const s = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
borderRadius: 12,
backgroundColor: '#111A2E',
},
avatar: {
width: 48,
height: 48,
borderRadius: 24,
},
info: {
flex: 1,
marginLeft: 12,
},
name: {
color: '#FFF',
fontSize: 16,
fontWeight: '700',
},
role: {
marginTop: 2,
color: '#B8C0CC',
fontSize: 13,
},
});O que observar:
flexDirection: 'row'define o eixo principal na horizontal.alignItems: 'center'centraliza verticalmente (eixo cruzado) avatar e textos.info: { flex: 1 }faz a área de texto ocupar o espaço restante.
Exemplo 2: espaçamento entre itens (gap) e alternativas
Em versões mais recentes do React Native, gap, rowGap e columnGap podem estar disponíveis. Quando disponível, simplifica espaçamentos entre filhos sem precisar de margin em cada item.
const s = StyleSheet.create({
actions: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 12,
},
});Se gap não estiver disponível no seu alvo, use uma destas abordagens:
- Margem no item (com cuidado para não duplicar margem no último): aplicar
marginRightem todos menos o último. - Componente Spacer: inserir um
<View style={{ width: 12 }} />entre itens. - justifyContent: quando fizer sentido, usar
space-betweenouspace-around(mas isso muda o comportamento conforme o espaço disponível).
Exemplo 3: responsividade com flex e quebras
Para listas de “chips”/tags que podem quebrar linha, use flexWrap:
const s = StyleSheet.create({
tags: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
},
tag: {
paddingHorizontal: 10,
paddingVertical: 6,
borderRadius: 999,
backgroundColor: '#1B2A4A',
},
tagText: {
color: '#D7DEEA',
fontSize: 12,
fontWeight: '600',
},
});Quando gap não existir, substitua por marginRight/marginBottom no estilo do item.
Composição de estilos: arrays e ordem de precedência
Você pode compor estilos com arrays: style={[base, variant, condition && extra]}. A regra é: o último estilo vence em caso de conflito.
Exemplo: botão com variantes e estado desabilitado
import { Pressable, Text, StyleSheet } from 'react-native';
export function Button({ title, variant = 'primary', disabled }) {
return (
<Pressable
disabled={disabled}
style={({ pressed }) => [
s.base,
variant === 'primary' ? s.primary : s.secondary,
pressed && !disabled && s.pressed,
disabled && s.disabled,
]}
>
<Text style={[s.text, variant === 'secondary' && s.textSecondary]}>
{title}
</Text>
</Pressable>
);
}
const s = StyleSheet.create({
base: {
height: 44,
paddingHorizontal: 14,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
primary: { backgroundColor: '#3B82F6' },
secondary: { backgroundColor: 'transparent', borderWidth: 1, borderColor: '#3B82F6' },
pressed: { opacity: 0.9, transform: [{ scale: 0.99 }] },
disabled: { opacity: 0.5 },
text: { color: '#FFFFFF', fontWeight: '700' },
textSecondary: { color: '#3B82F6' },
});Pontos-chave:
- O estilo do
Pressablepode ser uma função para reagir apressed. - Variantes ficam claras e evitam duplicação.
- O array permite combinar base + variações sem criar múltiplos componentes.
Estilos dinâmicos: quando usar e como evitar recriações desnecessárias
Estilos dinâmicos são úteis quando dependem de props (tamanho, cor, estado). Você pode:
- Compor com arrays e estilos pré-criados (preferível quando as variações são finitas).
- Criar estilos inline para valores realmente variáveis (ex.: largura calculada).
Exemplo: barra de progresso com largura dinâmica
import { View, StyleSheet } from 'react-native';
export function ProgressBar({ value }) {
const clamped = Math.max(0, Math.min(1, value));
return (
<View style={s.track}>
<View style={[s.fill, { width: `${clamped * 100}%` }]} />
</View>
);
}
const s = StyleSheet.create({
track: {
height: 10,
borderRadius: 999,
backgroundColor: '#1B2A4A',
overflow: 'hidden',
},
fill: {
height: '100%',
backgroundColor: '#22C55E',
},
});Note que a parte dinâmica é só a largura; o restante fica no StyleSheet.
Padronização visual com tokens (cores, tipografia, espaçamentos)
Tokens são valores padronizados (cores, tamanhos, espaçamentos) usados em todo o app. Eles evitam “números mágicos” e garantem consistência.
Passo a passo: criando tokens
1) Crie um arquivo theme/tokens.js:
export const colors = {
bg: '#0B1220',
surface: '#111A2E',
text: '#FFFFFF',
muted: '#B8C0CC',
primary: '#3B82F6',
success: '#22C55E',
border: '#22304F',
};
export const spacing = {
0: 0,
1: 4,
2: 8,
3: 12,
4: 16,
5: 20,
6: 24,
};
export const radius = {
sm: 8,
md: 12,
lg: 16,
pill: 999,
};
export const typography = {
title: { fontSize: 20, fontWeight: '700' },
body: { fontSize: 14, fontWeight: '500' },
caption: { fontSize: 12, fontWeight: '600' },
};2) Use tokens nos estilos:
import { StyleSheet } from 'react-native';
import { colors, spacing, radius, typography } from '../theme/tokens';
export const s = StyleSheet.create({
card: {
backgroundColor: colors.surface,
borderRadius: radius.lg,
padding: spacing[4],
borderWidth: 1,
borderColor: colors.border,
},
title: {
...typography.title,
color: colors.text,
},
caption: {
...typography.caption,
color: colors.muted,
marginTop: spacing[2],
},
});Repare no uso de ... para “mesclar” objetos de tipografia, mantendo consistência.
Criando um tema simples (claro/escuro) e aplicando em componentes
Um tema é um conjunto de tokens que pode variar (ex.: light/dark). Uma forma simples é exportar dois objetos e escolher com base no esquema do sistema.
Passo a passo: definindo temas
1) Crie theme/themes.js:
export const lightTheme = {
colors: {
bg: '#F6F7FB',
surface: '#FFFFFF',
text: '#0B1220',
muted: '#52607A',
primary: '#2563EB',
border: '#E3E7EF',
},
};
export const darkTheme = {
colors: {
bg: '#0B1220',
surface: '#111A2E',
text: '#FFFFFF',
muted: '#B8C0CC',
primary: '#3B82F6',
border: '#22304F',
},
};2) Crie um helper theme/useTheme.js usando useColorScheme:
import { useColorScheme } from 'react-native';
import { darkTheme, lightTheme } from './themes';
export function useTheme() {
const scheme = useColorScheme();
return scheme === 'dark' ? darkTheme : lightTheme;
}3) Aplique no componente (estilos dinâmicos com base no tema):
import { View, Text, StyleSheet } from 'react-native';
import { useTheme } from '../theme/useTheme';
import { spacing, radius, typography } from '../theme/tokens';
export function ThemedCard({ title, subtitle }) {
const theme = useTheme();
return (
<View style={[s.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>
<Text style={[s.title, { color: theme.colors.text }]}>{title}</Text>
<Text style={[s.subtitle, { color: theme.colors.muted }]}>{subtitle}</Text>
</View>
);
}
const s = StyleSheet.create({
card: {
borderWidth: 1,
borderRadius: radius.lg,
padding: spacing[4],
},
title: {
...typography.title,
},
subtitle: {
...typography.body,
marginTop: spacing[2],
},
});Esse padrão mantém a estrutura do layout no StyleSheet e troca apenas cores via tema.
Imagens responsivas: mantendo proporção e adaptando ao layout
Imagens podem distorcer se você não controlar dimensões e resizeMode. Boas práticas:
- Para imagens “capa”: use largura 100% e altura fixa (ou calculada) com
resizeModeadequado. - Para manter proporção: use
aspectRatio. - Para avatares: use
borderRadiusigual à metade do tamanho.
Exemplo: banner responsivo com aspectRatio
import { View, Image, StyleSheet } from 'react-native';
export function Banner() {
return (
<View style={s.container}>
<Image
source={{ uri: 'https://picsum.photos/1200/600' }}
style={s.image}
resizeMode="cover"
/>
</View>
);
}
const s = StyleSheet.create({
container: {
width: '100%',
borderRadius: 16,
overflow: 'hidden',
},
image: {
width: '100%',
aspectRatio: 2,
},
});Dica: overflow: 'hidden' é útil para recortar a imagem conforme o borderRadius.
Sombras, bordas e elevação: diferenças entre iOS e Android
Sombras têm APIs diferentes:
- iOS: usa
shadowColor,shadowOpacity,shadowRadius,shadowOffset. - Android: usa principalmente
elevation.
Exemplo: cartão com sombra cross-platform
import { StyleSheet, Platform } from 'react-native';
export const s = StyleSheet.create({
card: {
backgroundColor: '#111A2E',
borderRadius: 16,
padding: 16,
borderWidth: 1,
borderColor: '#22304F',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOpacity: 0.18,
shadowRadius: 12,
shadowOffset: { width: 0, height: 6 },
},
android: {
elevation: 6,
},
}),
},
});Cuidados:
- No Android,
elevationfunciona melhor combackgroundColordefinido. - Em iOS, sombras não aparecem se o componente tiver
overflow: 'hidden'. Se precisar de bordas arredondadas e sombra, uma estratégia é ter um wrapper com sombra e um filho interno comborderRadiuseoverflow: 'hidden'.
Densidade de tela e tamanhos: evitando UI “minúscula” ou “gigante”
Dispositivos variam em tamanho físico e densidade (DPI). Algumas práticas para manter consistência:
- Prefira espaçamentos e tamanhos em escala (tokens) em vez de valores aleatórios.
- Use
minHeight/minWidthpara áreas de toque confortáveis (ex.: botões com ~44px de altura). - Evite depender de
Dimensionspara tudo; use flexbox easpectRatioquando possível. - Quando precisar adaptar layout ao tamanho, use
useWindowDimensionspara reagir a rotação e mudanças.
Exemplo: ajuste simples por largura de tela
import { useWindowDimensions, View, StyleSheet } from 'react-native';
export function ResponsiveGrid({ children }) {
const { width } = useWindowDimensions();
const columns = width >= 768 ? 3 : width >= 420 ? 2 : 1;
return (
<View style={[s.grid, { gap: 12 }]}>
{Array.isArray(children)
? children.map((child, idx) => (
<View key={idx} style={{ width: `${100 / columns}%` }}>
{child}
</View>
))
: children}
</View>
);
}
const s = StyleSheet.create({
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
},
});Se gap não estiver disponível, substitua por margens nos itens.
Práticas para evitar estilos duplicados e manter legibilidade
- Crie estilos base reutilizáveis: por exemplo,
cardBase,row,center,screenPadding. Use composição por arrays para combinar. - Use tokens para tudo que se repete: cores, espaçamentos, raio, tipografia. Se você digitou a mesma cor hex mais de 2 vezes, ela provavelmente deveria virar token.
- Nomeie estilos por intenção, não por aparência:
header,price,containerem vez deblueText,bigMargin. - Evite inline para estilos estáticos: inline é útil para valores realmente variáveis; o resto vai para
StyleSheet. - Centralize variantes: em vez de duplicar componentes, use
variant+ composição de estilos (ex.: botão primary/secondary). - Quebre estilos grandes: se um arquivo de estilos ficar enorme, separe por domínio (ex.:
Profile.styles.js,Button.styles.js) ou por componente. - Padronize espaçamentos: escolha uma escala (4, 8, 12, 16...) e siga. Isso melhora alinhamento visual e acelera decisões.
- Revise alinhamentos com flexbox: antes de adicionar margens “para empurrar”, verifique se
justifyContent,alignItemseflexresolvem de forma mais robusta.
| Problema comum | Sinal | Melhor abordagem |
|---|---|---|
| Espaçamento inconsistente | Vários valores aleatórios (7, 13, 19) | Tokens de spacing e uso consistente |
| Layouts quebrando em telas diferentes | Uso excessivo de width/height fixos | Flexbox, flex: 1, aspectRatio, useWindowDimensions quando necessário |
| Estilos duplicados | Mesmas propriedades copiadas em vários lugares | Estilos base + composição por arrays + tokens |
| Sombras inconsistentes | Boa no iOS, ruim no Android (ou vice-versa) | Platform.select com elevation e propriedades de sombra |