Caracteres Unicode invisíveis: o que são e o que quebram

Uma referência prática sobre os caracteres que estão no seu texto, mas não na sua tela.

Caracteres Unicode invisíveis não ocupam espaço visível, mas continuam fazendo parte da string. Os mais comuns são espaços de largura zero, hífens suaves, marcas de ordem de bytes, espaços inquebráveis e marcas direcionais. Cada um tem uma função tipográfica legítima, e cada um quebra buscas, validações, análises e deduplicação quando sobrevive a um copiar e colar.

Os caracteres que você vai encontrar de verdade

Caractere Ponto de código Função O que quebra
Espaço de largura zero U+200B Sugere um ponto de quebra de linha Busca, comparação de strings
Não-conector de largura zero U+200C Impede que letras se unam Ligaduras, texto em árabe e índico
Conector de largura zero U+200D Força letras a se unirem Sequências de emoji, tamanho do texto
Hífen suave U+00AD Hífen exibido só na quebra de linha Busca, texto copiado
Espaço inquebrável U+00A0 Espaço que nunca quebra Trimming, parsing de CSV, código
Espaço inquebrável estreito U+202F Espaço fino antes de pontuação Igual ao anterior
Marca de ordem de bytes U+FEFF Marca a codificação no início do arquivo Primeira coluna do CSV, parsing de JSON
Unidor de palavras U+2060 Impede quebra, sem largura Busca
Marca da esquerda para a direita U+200E Define a direção do texto Ordem de exibição, trimming
Marca da direita para a esquerda U+200F Define a direção do texto Ordem de exibição
Override RTL U+202E Força exibição da direita para a esquerda Falsificação de nomes de arquivo
Separador de linha U+2028 Quebra de linha Unicode Strings literais em JavaScript
Separador de parágrafo U+2029 Quebra de parágrafo Unicode JSON em parsers antigos

Cada um desses caracteres tem uma função real. O problema nunca é a existência deles — é que eles viajam silenciosamente entre sistemas que os tratam de forma diferente.

De onde eles vêm

  • Copiar de páginas da web. Sites inserem espaços de largura zero para controlar onde strings longas quebram, e hífens suaves para texto justificado. Os dois vêm junto na cópia.
  • Editores de texto. Espaços inquebráveis são inseridos automaticamente para manter um número junto da sua unidade, ou um nome em uma só linha.
  • Extração de PDF. Copiar de um PDF costuma gerar hífens suaves onde o diagramador quebrou palavras entre linhas, além de espaços inquebráveis vindos do espaçamento justificado.
  • Interfaces de chat de IA. O resultado é renderizado como HTML, então a cópia carrega os caracteres de espaçamento que a formatação usou — geralmente U+00A0 e U+202F.
  • Conversões de codificação. Uma marca de ordem de bytes aparece quando um arquivo é salvo como UTF-8 com BOM, algo comum no Windows.
  • Inserção deliberada. Caracteres de largura zero já foram usados para marcar documentos com o objetivo de rastrear vazamentos, e para fazer texto passar por filtros de palavras-chave.

O padrão é consistente: eles entram quando o texto sai de um contexto de apresentação e vai para um contexto de dados.

Como eles quebram as coisas

A busca falha em silêncio. Um espaço de largura zero dentro de uma palavra faz a busca por ela não retornar nada. Nada indica o motivo — a palavra está visivelmente ali.

A validação rejeita entradas corretas. Um endereço de e-mail com um espaço inquebrável no final não passa na verificação de formato. O usuário vê um endereço correto e uma mensagem de erro que não faz sentido.

Comparações falham. "Ada" === "Ada\u200B" é falso. A deduplicação não pega duplicatas óbvias; buscas não retornam nada; junções perdem linhas.

O trimming não ajuda. A maioria das implementações de trim() remove apenas espaços em branco ASCII. Um espaço inquebrável sobrevive ao .trim() em muitas linguagens, e é por isso que "mas eu já fiz o trim" é um beco sem saída tão comum.

O código não compila. Um espaço inquebrável onde deveria haver um espaço normal gera um erro de sintaxe em uma linha que parece perfeita.

Importações de CSV corrompem. Uma marca de ordem de bytes gruda no primeiro cabeçalho, então id vira \uFEFFid e a primeira coluna falha silenciosamente no mapeamento.

O tamanho fica errado. Conectores de largura zero em sequências de emoji fazem um único emoji visível ter vários pontos de código, então limites de caracteres rejeitam entradas que parecem estar dentro do permitido.

Nomes de arquivo podem enganar. U+202E inverte o texto exibido, então report\u202Egnp.exe pode aparecer como reportexe.png. Isso é uma preocupação real de segurança, não uma curiosidade.

Como encontrá-los e removê-los

Analise primeiro. Cole o texto no verificador de caracteres invisíveis. Ele informa quais tipos estão presentes e quantos de cada um, e depois os remove quando você pedir. Ver as contagens antes mostra se você está lidando com um caractere perdido ou com uma contaminação sistemática.

No editor. O VS Code destaca a maioria dos caracteres invisíveis por padrão, por meio da configuração unicode-highlight. Para localizar e substituir com regex, este padrão pega o conjunto mais comum:

[\u200B-\u200F\u00AD\uFEFF\u2060\u202A-\u202E\u2028\u2029]

No código, normalize na fronteira — quando o dado chega, não quando é usado:

const clean = input
  .replace(/[\u200B-\u200F\u2060\uFEFF\u00AD\u202A-\u202E]/g, "")
  .replace(/[\u00A0\u202F]/g, " ")
  .normalize("NFC")
  .trim();

Repare no tratamento em duas etapas: caracteres de largura zero são removidos, mas espaços inquebráveis são convertidos em espaços comuns — removê-los juntaria as palavras.

Não remova cegamente. Conectores de largura zero são estruturais em sequências de emoji e em árabe, persa e muitas escritas índicas, onde removê-los muda a forma como as palavras são renderizadas. Remova com agressividade em identificadores, chaves e código; remova com cautela em conteúdo voltado ao usuário.

Para a questão específica de se ferramentas de IA inserem esses caracteres de propósito, veja o ChatGPT esconde caracteres invisíveis?. O equivalente visível, mas igualmente problemático, está em por que aspas curvas quebram código.

Perguntas frequentes

O que é um espaço de largura zero?

U+200B, um caractere que não ocupa largura visível, mas continua fazendo parte da string. Sites o usam para sugerir onde uma palavra longa pode quebrar. Ele quebra buscas e comparações de strings porque o texto parece idêntico, embora os bytes por baixo sejam diferentes.

Por que minha busca falha em um texto que está claramente ali?

Quase certamente há um caractere invisível dentro da palavra — geralmente um espaço de largura zero ou um hífen suave vindo de uma página da web ou PDF. O texto renderizado corresponde ao que você digitou, mas a string por baixo não.

O trim() remove espaços inquebráveis?

Normalmente não. A maioria das implementações de trim remove apenas espaços em branco ASCII, então um espaço inquebrável (U+00A0) sobrevive. É por isso que uma entrada que aparentemente não tem espaços no início ou no fim ainda falha na validação depois do trim.

Como removo caracteres invisíveis de um texto?

Cole-o em um verificador que liste e remova esses caracteres, ou use uma regex cobrindo U+200B–U+200F, U+00AD, U+FEFF, U+2060 e U+202A–U+202E. Remova os caracteres de largura zero, mas converta espaços inquebráveis em espaços comuns em vez de removê-los, senão as palavras vão grudar.

Caracteres invisíveis são úteis em alguma situação?

Sim. Hífens suaves controlam quebras de palavras em texto justificado, espaços inquebráveis mantêm valores junto das suas unidades, e conectores de largura zero são essenciais para sequências de emoji e para as escritas árabe, persa e índicas. Remova com agressividade em código e identificadores, e com cautela em prosa.

Ferramentas mencionadas neste guia

Guias relacionados