# Caracteres invisíveis: por que o texto colado quebra o código

> Um e-mail colado que não passa na validação, uma comparação que dá falso entre textos iguais, um JSON que não abre: muitas vezes a culpa é de um caractere invisível, como o espaço de largura zero (U+200B), o BOM (U+FEFF) ou o espaço não separável (U+00A0). Este guia mostra quais são os mais comuns, de onde vêm, como encontrá-los e como removê-los em JavaScript, Python e PHP.

- Endereço oficial: https://pradev.com.br/guias/caracteres-invisiveis-no-texto-colado
- Tipo: Guia
- Publicado em: 2026-10-01
- Atualizado em: 2026-10-01
- Autor: Leonardo Gaertner (https://pradev.com.br/sobre)
- Ferramentas: [Informações de Caractere](https://pradev.com.br/informacoes-de-caractere), [Remover Espaços](https://pradev.com.br/remover-espacos), [Contador de Caracteres](https://pradev.com.br/contador-de-caracteres)

## Os suspeitos de sempre

- `U+00A0`, espaço não separável: vem do `&nbsp;` de páginas e e-mails e de números formatados, como "1 000". Parece um espaço, mas não é igual a ele.
- `U+200B`, espaço de largura zero: não aparece. Vem de editores, sites e apps de mensagem, que o usam para permitir quebra de linha em palavras longas.
- `U+FEFF`, BOM (marca de ordem de bytes): fica no começo de arquivos salvos em "UTF-8 com BOM", como no Bloco de Notas antigo e no Excel. Quebra JSON e cabeçalhos de CSV.
- `U+00AD`, hífen opcional: só aparece quando a palavra quebra no fim da linha, mas continua no texto copiado.
- `U+200D` e `U+200C`: unem e separam caracteres, como os emojis de família. Dentro de um código ou de uma senha, só atrapalham.

## Os sintomas

O texto parece certo, mas a comparação falha, a busca não encontra, a validação de e-mail recusa ou o `JSON.parse` acusa um erro na posição 0. O [contador de caracteres](https://pradev.com.br/contador-de-caracteres) dá uma pista: ele conta um caractere a mais do que você vê. Para ver qual é, cole o trecho na ferramenta de [informações de caractere](https://pradev.com.br/informacoes-de-caractere), que mostra o código de cada um e destaca os invisíveis.

## Encontrando e removendo em código

A estratégia segura tem duas partes: trocar os espaços especiais por um espaço comum (para não grudar palavras) e apagar os caracteres de largura zero. Use isso na entrada de dados que vêm de cópia e cola, como e-mails, nomes de usuário, códigos e chaves.

```javascript
// Espaços que parecem comuns, mas não são, e caracteres que nem aparecem na tela.
const ESPACOS_ESPECIAIS = /[   -   　]/g;
const INVISIVEIS = /[​‌‍⁠﻿­]/g;

// Lista o que encontrou, com o código de cada caractere (U+200B...)
function encontrarInvisiveis(texto) {
  return [...texto.matchAll(new RegExp(`${ESPACOS_ESPECIAIS.source}|${INVISIVEIS.source}`, 'g'))].map((m) => ({
    posicao: m.index,
    codigo: 'U+' + m[0].codePointAt(0).toString(16).toUpperCase().padStart(4, '0'),
  }));
}

// Troca os espaços especiais por espaço comum e remove os invisíveis.
function limparInvisiveis(texto) {
  return texto.replace(ESPACOS_ESPECIAIS, ' ').replace(INVISIVEIS, '');
}

const colado = 'maria​@exemplo.com';
console.log(colado === 'maria@exemplo.com'); // false
console.log(encontrarInvisiveis(colado)); // [ { posicao: 5, codigo: 'U+200B' } ]
console.log(limparInvisiveis(colado) === 'maria@exemplo.com'); // true
```

_JavaScript. Lista as posições e limpa o texto._

```python
import re

ESPACOS_ESPECIAIS = re.compile("[   -   　]")
INVISIVEIS = re.compile("[​‌‍⁠﻿­]")


def encontrar_invisiveis(texto: str) -> list[tuple[int, str]]:
    """Posição e código (U+200B...) de cada espaço especial ou caractere invisível."""
    padrao = re.compile(f"{ESPACOS_ESPECIAIS.pattern}|{INVISIVEIS.pattern}")
    return [(m.start(), f"U+{ord(m.group()):04X}") for m in padrao.finditer(texto)]


def limpar_invisiveis(texto: str) -> str:
    return INVISIVEIS.sub("", ESPACOS_ESPECIAIS.sub(" ", texto))


colado = "maria​@exemplo.com"
print(colado == "maria@exemplo.com")  # False
print(encontrar_invisiveis(colado))  # [(5, 'U+200B')]
print(limpar_invisiveis(colado) == "maria@exemplo.com")  # True
```

_Python_

```php
<?php
// Troca os espaços especiais por espaço comum e remove os caracteres invisíveis.
// O modificador u faz o PHP tratar o texto como UTF-8.
function limparInvisiveis(string $texto): string
{
    $texto = preg_replace('/[\x{00A0}\x{1680}\x{2000}-\x{200A}\x{202F}\x{205F}\x{3000}]/u', ' ', $texto);
    return preg_replace('/[\x{200B}\x{200C}\x{200D}\x{2060}\x{FEFF}\x{00AD}]/u', '', $texto);
}

$colado = "maria\u{200B}@exemplo.com";
var_dump($colado === 'maria@exemplo.com'); // bool(false)
var_dump(limparInvisiveis($colado) === 'maria@exemplo.com'); // bool(true)
```

_PHP. O modificador u é obrigatório para o PHP ler o texto como UTF-8._

Para limpar um texto sem programar, a ferramenta de [remover espaços](https://pradev.com.br/remover-espacos) faz a mesma troca e diz quantos caracteres especiais encontrou.

## Cuidados

- Não remova o `U+200D` de textos com emoji: ele junta 👨‍👩‍👧 em um só desenho. Limpe só campos técnicos, como e-mails e códigos.
- O `U+00A0` às vezes é desejado, como entre o número e a unidade ("10 km"), para não quebrar a linha. Em texto para exibição, pode ficar.
- Ao ler arquivos, prefira tratar o BOM na leitura: em Python, `open(arquivo, encoding="utf-8-sig")` remove o BOM sozinho.

## Perguntas frequentes

### Por que meu JSON dá erro na posição 0?

Quase sempre por causa do BOM, o caractere U+FEFF no começo do arquivo. Salve o arquivo como UTF-8 sem BOM ou remova o primeiro caractere antes de ler.

### Como saber se um texto tem caracteres invisíveis?

Cole o texto na ferramenta de informações de caractere: os invisíveis aparecem destacados, com o código U+ de cada um. Em código, procure pelos códigos listados neste guia.

### trim() remove esses caracteres?

Em parte. O trim do JavaScript remove o espaço não separável e o BOM das pontas, mas não remove o espaço de largura zero, e nada do meio do texto. O strip do Python também não remove o U+200B.

## Fontes

- [Unicode: perguntas frequentes sobre o BOM e o UTF-8](https://www.unicode.org/faq/utf_bom.html)
- [MDN (em inglês): String.prototype.trim e os espaços que ele remove](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim)

## Guias relacionados

- [Como gerar slug de URL a partir de texto com acentos](https://pradev.com.br/guias/como-gerar-slug-de-url-com-acentos): Como transformar um título com acentos em slug de URL (Ação e Reação vira acao-e-reacao) em JavaScript, Python e PHP, e os cuidados com slugs repetidos.
- [Como formatar e validar JSON em JavaScript, Python e PHP](https://pradev.com.br/guias/como-formatar-e-validar-json): Como deixar JSON legível e descobrir o erro de um JSON inválido em JavaScript, Python e PHP, com os erros mais comuns e as diferenças entre as linguagens.
