Estilização em React Native: StyleSheet, flexbox e design consistente

Capítulo 4

Tempo estimado de leitura: 10 minutos

+ Exercício

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 (row ou column).
  • 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):

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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 marginRight em todos menos o último.
  • Componente Spacer: inserir um <View style={{ width: 12 }} /> entre itens.
  • justifyContent: quando fizer sentido, usar space-between ou space-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 Pressable pode ser uma função para reagir a pressed.
  • 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 resizeMode adequado.
  • Para manter proporção: use aspectRatio.
  • Para avatares: use borderRadius igual à 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, elevation funciona melhor com backgroundColor definido.
  • 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 com borderRadius e overflow: '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/minWidth para áreas de toque confortáveis (ex.: botões com ~44px de altura).
  • Evite depender de Dimensions para tudo; use flexbox e aspectRatio quando possível.
  • Quando precisar adaptar layout ao tamanho, use useWindowDimensions para 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, container em vez de blueText, 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, alignItems e flex resolvem de forma mais robusta.
Problema comumSinalMelhor abordagem
Espaçamento inconsistenteVários valores aleatórios (7, 13, 19)Tokens de spacing e uso consistente
Layouts quebrando em telas diferentesUso excessivo de width/height fixosFlexbox, flex: 1, aspectRatio, useWindowDimensions quando necessário
Estilos duplicadosMesmas propriedades copiadas em vários lugaresEstilos base + composição por arrays + tokens
Sombras inconsistentesBoa no iOS, ruim no Android (ou vice-versa)Platform.select com elevation e propriedades de sombra

Agora responda o exercício sobre o conteúdo:

Ao combinar estilos com arrays em React Native (por exemplo, style={[base, variant, condition && extra]}), como funciona a precedência quando há conflito entre propriedades?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Ao compor estilos com arrays, o React Native aplica os objetos na ordem. Se duas regras definirem a mesma propriedade, a que aparecer por último no array prevalece.

Próximo capitúlo

Navegação em React Native com boas práticas usando React Navigation

Arrow Right Icon
Capa do Ebook gratuito React Native Essencial: criando apps completos com JavaScript e boas práticas
25%

React Native Essencial: criando apps completos com JavaScript e boas práticas

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.