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.