Dia 3 — Texto e tipografia

Informatica · Conteudo · publicado em 05/10/2026
Dia 3 de 16

Texto e tipografia

Aula 1

fontSize, fontWeight e cor

fontSize, fontWeight e cor

fontSize é o tamanho em pixels e é o número que mais aparece em style do

Text. A escala da interface do sistema não usa 12 e 18: usa um degrau, e o

primeiro sintoma de uma tela amadora é o Text com três tamanhos que não

pertencem a nenhum degrau. fontWeight aceita os nomes 'normal', 'bold',

'100' a '900', e '500' a '700' é a faixa que o Android e o iOS respeitam

igual — acima de '700' o iOS costuma engrossar tudo, o que produz um título

mais pesado no celular da Apple do que no Android, sem que nada tenha mudado no

código.

color no Text é cor de texto; a cor de fundo é backgroundColor e pertence

ao View. Confundir os dois é o erro mais comum da aula: backgroundColor

num Text pinta um retângulo em volta das palavras, e color num View não

faz nada porque o View não desenha texto. Quando o Text é filho do View, a

cor do texto não é herdada — cada Text declara a sua.

O exemplo desta página monta um cartão com quatro níveis de texto e imprime, para

cada um, o fontSize e o fontWeight alinhados em coluna: 22 px com 700

para "Pedido 41 confirmado", 15 px com 400, 12 px com 500 e 11 px com

600 para a etiqueta "EM ROTA". São quatro degraus, não três, e cada um com um

papel: título, corpo, nota e etiqueta. O color de cada um está no mesmo objeto

do tamanho, e é o que faz o View do cartão precisar de padding e não de cor.

O quarto nível é o que prova a separação entre texto e fundo: a etiqueta usa

color: '#ffffff' e backgroundColor: '#1f6feb', e as duas propriedades

moram no mesmo Text. Uma tela inteira com a cor só no Text tem o

backgroundColor no View que o contém — e aí o retângulo azul pinta o cartão

inteiro, não a palavra.

const estilos = StyleSheet.create({
  titulo: { fontSize: 22, fontWeight: '700', color: '#101418' },
  corpo: { fontSize: 15, fontWeight: '400', color: '#57606a' },
  nota: { fontSize: 12, fontWeight: '500', color: '#8b949e' },
});

Os quatro níveis do exemplo são o argumento contra a escala ad hoc: o

fontSize de cada um se explica pelo papel que a peça cumpre na tela, e não

por gosto. 22 é o título porque é o texto que o olho pega primeiro; 15 é o

corpo porque é o texto que se lê em bloco; 12 é a nota porque é informação

secundária; 11 é a etiqueta porque é texto dentro de uma forma.

fontFamily e o nome que o aparelho não tem

fontFamily recebe o nome de uma fonte instalada no aparelho, e não um

caminho de arquivo. No Android, uma fonte que não existe no sistema é

silenciosamente ignorada: o Text sai na fonte padrão e ninguém recebe erro. No

iOS o nome da fonte é o nome interno do arquivo, que quase nunca é o nome que

está escrito na pasta. A fonte que acompanha o app entra como arquivo no

projeto, é registrada no arquivo de configuração de cada plataforma e só depois

passa a valer pelo fontFamily.

A consequência prática é que fontFamily é a única destas propriedades que

pode não ter efeito nenhum sem erro nenhum. fontSize errado produz texto

maior; fontWeight desconhecido produz o peso mais próximo; fontFamily

errado produz exatamente a fonte padrão, indistinguível de não ter escrito

nada. Por isso o teste que economiza tempo é o mesmo: comentar a propriedade,

comparar a tela, e só depois investigar o nome.

O detalhe que economiza tempo: antes de depurar fontFamily, testar sem a

propriedade. Se o resultado muda, o nome estava errado; se não muda, o problema

está em outro lugar.

O mesmo vale para letterSpacing, que o exemplo já aplica na nota: 0.4 é um

número pequeno, e o efeito é abrir as letras o suficiente para a etiqueta estreita

caber sem parecer outro texto. Ele não muda o tamanho nem o peso — só o espaço

entre os glifos.

lineHeight é o espaço entre as linhas

lineHeight não é o tamanho da fonte: é a altura de cada linha. Aumentar

lineHeight deixa o parágrafo arejado sem aumentar a letra, e é a diferença

entre um texto longo que dá vontade de ler e um bloco compacto que cansa. Em

português, com cedilha e acento, a linha precisa de ar para a haste da letra

não encostar na seguinte: fontSize: 15 com lineHeight: 22 é o degrau

confortável, e lineHeight menor que fontSize corta os acentos de cima.

O exemplo fecha a conta com a função linhasOcupadas, que multiplica linhas por

lineHeight e devolve 66 para três linhas de 22. É o mesmo cálculo que o

Text faz de verdade quando o conteúdo quebra, e é por isso que lineHeight

determina a altura do parágrafo de forma previsível: o número de linhas depende

da largura, a altura de cada linha não.

E o caso do erro está no último console: fontSize: 15 com lineHeight: 13

imprime os dois números juntos, e a leitura é que 13 é menor que 15. Cada linha

tem 13 pixels para uma letra que precisa de 15, e o desenho corta o topo — o

acento, o til, a barra do ç. Não é defeito do aparelho: é a conta fechando

errada por dois pixels.

PropriedadeControla
fontSizeo tamanho da letra
fontWeighta espessura do traço
lineHeighta distância entre uma linha e a seguinte
letterSpacingo espaço entre as letras, útil em rótulo de botão
colora cor do texto

Exemplo

// O trio que define o texto: `fontSize`, `fontWeight` e `color`. O
// exemplo monta uma hierarquia de tres niveis e le o estilo que cada
// `Text` levou ate a arvore.
const { View, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({
  cartao: { padding: 16 },
  titulo: { fontSize: 22, fontWeight: '700', color: '#101418', lineHeight: 28 },
  corpo: { fontSize: 15, fontWeight: '400', color: '#57606a', lineHeight: 22 },
  nota: { fontSize: 12, fontWeight: '500', color: '#8b949e', letterSpacing: 0.4 },
  cota: { fontSize: 11, fontWeight: '600', color: '#ffffff', backgroundColor: '#1f6feb' },
});

function Cartao() {
  return (
    <View style={estilos.cartao}>
      <Text style={estilos.titulo}>Pedido 41 confirmado</Text>
      <Text style={estilos.corpo}>O desenho saiu para producao.</Text>
      <Text style={estilos.nota}>atualizado agora</Text>
      <Text style={estilos.cota}>EM ROTA</Text>
    </View>
  );
}

const arvore = Cartao();
console.log('tipo da raiz:', arvore.type);
console.log('quantos filhos de texto:', arvore.props.children.length);

const titulo = arvore.props.children[0];
console.log('titulo:', titulo.type, StyleSheet.flatten(titulo.props.style));

// O texto de cada nivel e o que o aluno le no `props.children`.
for (const filho of arvore.props.children) {
  const estilo = StyleSheet.flatten(filho.props.style);
  console.log(
    String(estilo.fontSize).padStart(4),
    'px',
    String(estilo.fontWeight).padStart(4),
    '|',
    filho.props.children
  );
}

// `lineHeight` e o espaco entre linhas: menor que `fontSize` corta acento.
function linhasOcupadas(linhas, estilo) {
  return linhas * estilo.lineHeight;
}

console.log('tres linhas com lineHeight 22:', linhasOcupadas(3, { lineHeight: 22 }));
console.log('lineHeight 13 com fontSize 15 corta o acento:', StyleSheet.flatten({ fontSize: 15, lineHeight: 13 }));

Saída real

tipo da raiz: View
quantos filhos de texto: 4
titulo: Text { fontSize: 22, fontWeight: '700', color: '#101418', lineHeight: 28 }
  22 px  700 | Pedido 41 confirmado
  15 px  400 | O desenho saiu para producao.
  12 px  500 | atualizado agora
  11 px  600 | EM ROTA
tres linhas com lineHeight 22: 66
lineHeight 13 com fontSize 15 corta o acento: { fontSize: 15, lineHeight: 13 }
Aula 2

textAlign, numberOfLines e o texto longo

textAlign, numberOfLines e o texto longo

textAlign alinha o texto dentro do Text, e vale 'left', 'right',

'center' e 'justify' — este último só tem efeito quando o texto quebra em

várias linhas. Ele não move o Text na tela: para isso é alignSelf no pai, ou

o Text dentro de um View com alignItems: 'center'. Confundir os dois é o

erro mais comum da aula, e a diferença aparece na hora de centralizar um

parágrafo inteiro, que precisa dos dois: o View centraliza a caixa do texto e

o textAlign centraliza as linhas dentro da caixa.

numberOfLines limita o número de linhas e corta o resto com reticências. É a

propriedade que mantém uma lista com altura previsível, e o valor exato depende

do desenho: numberOfLines={2} para o título de um item e numberOfLines={4}

para o texto corrido. ellipsizeMode diz onde cortar: 'tail' no fim, que

é o padrão, 'head' no começo, útil quando o fim do texto é o que importa, e

'middle' no meio.

O exemplo desta página monta um item de lista com foto, título e resumo, e as

propriedades de corte chegam intactas na árvore: o título declara

numberOfLines igual a 1 e o resumo igual a 2, e os dois com

ellipsizeMode 'tail'. As duas propriedades são props do Text, não chaves

do StyleSheet — e é por isso que elas não aparecem no objeto de estilo: o que

está no estilo é o textAlign, que o exemplo imprime como left para o título.

O contraste do mesmo item é o que dá sentido aos números: o título de 44

caracteres corta em uma linha e o resumo de 90 corta em duas. Os dois textos são

do mesmo tamanho na tela, e mesmo assim o número de linhas vale um para um e dois

para o outro. O que decide é o papel da peça na tela, não o comprimento do dado.

<Text numberOfLines={2} ellipsizeMode="tail">
  {descricao}
</Text>

A propriedade ellipsizeMode só aparece quando o corte acontece de fato. Com

numberOfLines={2} e um texto de uma linha só, sobra linha e o tail não é

desenhado — o valor está declarado e não produz nada. É a mesma relação que

existe entre borderColor e borderWidth: um declara a intenção, o outro

declara que há pixel para ela.

a altura que o texto ocupa de verdade

O Text sem numberOfLines cresce conforme o conteúdo, e o View que o

contém herda esse crescimento. Do mesmo texto em três larguras diferentes saem

três alturas, e é por isso que um card que "pula" ao carregar mais item quase

sempre tem Text sem limite de linhas. Com numberOfLines fixo, a altura fica

determinada por fontSize vezes lineHeight vezes o número de linhas, e o

card não se move.

A função altura do exemplo faz essa multiplicação em três casos, e os números

mostram por que o limite importa: com lineHeight: 20 e uma linha o título tem

20 de altura, e com três linhas tem 60. O resumo, com lineHeight: 18 e duas

linhas, dá 36. Nenhum desses números depende do conteúdo — dependem de

lineHeight e do limite declarado, que é o que torna a altura previsível.

A demonstração do outro lado usa a função linhasAte, que quebra um texto de 51

caracteres em pedaços de tamanho fixo e conta quantos pedaços saem: 3 linhas

com 20 caracteres por linha, 5 linhas com 12. O mesmo texto produz alturas

diferentes conforme a largura, e é exatamente isso que o numberOfLines trava:

com o limite em 2, o título nunca passa de 40 de altura, aconteça o que

acontecer com a largura da tela.

O detalhe do corte: numberOfLines conta linhas renderizadas, e o React

Native mede a largura disponível antes de quebrar. Se o Text estiver dentro de

um View sem largura definida, o cálculo parte da largura da tela inteira e o

resultado muda conforme o conteúdo dos vizinhos.

O exemplo trava a altura pelo lado do pai, e é o que o conserto exige: a coluna

do texto declara flex: 1, que é o que garante que ela meça a largura que sobra

depois da foto de 80. Sem essa declaração, a coluna mediria a largura do

conteúdo e o numberOfLines contaria linhas de um tamanho que não existe na

tela.

quando o texto precisa ser todo lido

Truncar é a decisão certa para item de lista e a decisão errada para o corpo de

uma mensagem. Três saídas para o texto que não pode ser cortado: remover o

numberOfLines e deixar o View crescer; pôr o texto dentro de um ScrollView

para a rolagem assumir; ou declarar flexShrink: 1 no Text dentro de uma

linha, que é o que impede o texto de passar da borda quando ele divide espaço

com uma imagem.

flexShrink merece atenção porque resolve um defeito que parece de estilo e é

de tamanho: um Text ao lado de um Image com width: 80 e flex: 1 divide

o espaço com a imagem, mas um Text com conteúdo longo não encolhe por padrão

e transborda. flexShrink: 1 devolve a ele a capacidade de encolher e quebrar

linha dentro da fatia que sobrou.

A ordem das duas propriedades é o detalhe que separa o item de lista do corpo de

mensagem. O flexShrink: 1 não impede o corte — ele impede o transbordo. Com os

dois, o texto encolhe até a fatia que sobrou da foto e aí quebra, e o

numberOfLines decide até onde vai. Sem o numberOfLines, o mesmo flexShrink

deixa o texto ocupar todas as linhas que precisar dentro daquela fatia, que é o

comportamento certo para uma descrição longa.

Exemplo

// `numberOfLines` corta o texto depois de N linhas e `textAlign` alinha
// as linhas DENTRO do `Text`. O exemplo calcula a altura que o texto
// ocupa e mostra o corte da lista de tamanho fixo.
const { View, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({
  linha: { flexDirection: 'row', alignItems: 'center', padding: 12 },
  foto: { width: 80, height: 80, borderRadius: 8 },
  coluna: { flex: 1, marginLeft: 12 },
  titulo: { fontSize: 16, fontWeight: '600', textAlign: 'left', lineHeight: 20 },
  resumo: { fontSize: 13, color: '#57606a', lineHeight: 18 },
});

function Item(props) {
  return (
    <View style={estilos.linha}>
      <View style={estilos.foto} />
      <View style={estilos.coluna}>
        <Text style={estilos.titulo} numberOfLines={1} ellipsizeMode="tail">
          {props.titulo}
        </Text>
        <Text style={estilos.resumo} numberOfLines={2} ellipsizeMode="tail">
          {props.resumo}
        </Text>
      </View>
    </View>
  );
}

const arvore = Item({
  titulo: 'Bolo de cenoura com cobertura de chocolate',
  resumo: 'Massa fofinha de cenoura com cenoura ralada na massa e cobertura de chocolate meio amarga servida gelada.',
});
console.log('tipo da raiz:', arvore.type);

// as props de corte vao no `Text` e chegam intactas na arvore
const coluna = arvore.props.children[1];
const titulo = coluna.props.children[0];
const resumo = coluna.props.children[1];
console.log('titulo corta em', titulo.props.numberOfLines, 'linha(s), no', titulo.props.ellipsizeMode);
console.log('resumo corta em', resumo.props.numberOfLines, 'linha(s), no', resumo.props.ellipsizeMode);
console.log('align do titulo:', StyleSheet.flatten(titulo.props.style).textAlign);

// A altura com `numberOfLines` e previsivel: linhas vezes `lineHeight`.
function altura(estilo, numeroDeLinhas) {
  return numeroDeLinhas * estilo.lineHeight;
}

console.log('titulo com lineHeight 20 e 1 linha:', altura({ lineHeight: 20 }, 1));
console.log('titulo com lineHeight 20 e 3 linhas:', altura({ lineHeight: 20 }, 3));
console.log('resumo com lineHeight 18 e 2 linhas:', altura({ lineHeight: 18 }, 2));

// Sem `numberOfLines`, a altura depende do conteudo: e o card que "pula".
function linhasAte(texto, caracteresPorLinha) {
  const linhas = [];
  for (let i = 0; i < texto.length; i += caracteresPorLinha) {
    linhas.push(texto.slice(i, i + caracteresPorLinha));
  }
  return linhas.length;
}

const longo = 'Massa fofinha de cenoura com cobertura de chocolate.';
console.log('linhas com 20 caracteres por linha:', linhasAte(longo, 20));
console.log('linhas com 12 caracteres por linha:', linhasAte(longo, 12));
console.log('a largura muda o numero de linhas, e o `numberOfLines` trava isso');

Saída real

tipo da raiz: View
titulo corta em 1 linha(s), no tail
resumo corta em 2 linha(s), no tail
align do titulo: left
titulo com lineHeight 20 e 1 linha: 20
titulo com lineHeight 20 e 3 linhas: 60
resumo com lineHeight 18 e 2 linhas: 36
linhas com 20 caracteres por linha: 3
linhas com 12 caracteres por linha: 5
a largura muda o numero de linhas, e o `numberOfLines` trava isso