Olha só isso: finalmente dá pra estilizar qualquer texto
Sabe quando você quer destacar o resultado de uma busca na página e a única saída era sair injetando <span> ou <mark> no DOM? Pois é, essa era acabou. 🎉
A CSS Custom Highlight API chegou pra resolver exatamente isso. Combinada com pseudo-elementos como ::search-text, ::highlight(), ::target-text, ::spelling-error e ::grammar-error, você agora consegue estilizar ranges de texto que nunca foram envolvidos por nenhum elemento — só com CSS puro.
Isso muda o jogo pra ferramentas de busca, leitores de artigo, editores colaborativos e qualquer coisa que precise marcar texto sem mexer no DOM. Se você já escreveu aquele wrapper em cima do mark.js só pra pintar um resultado de busca, respira: tem caminho melhor. E se você quer entender como a plataforma vem evoluindo nessa direção, dá uma olhada nesse guia sobre CSS highlight pseudo-elements.

Os pseudo-elementos que você precisa conhecer
Segue o mapa da mina, do mais estável pro mais experimental:
| Pseudo-elemento | Pra que serve | Suporte |
|---|---|---|
::selection | Texto selecionado pelo usuário | Universal |
::target-text | Texto alvo via fragmento de URL | Chrome, Safari |
::search-text | Achados do "localizar na página" | Chromium |
::spelling-error / ::grammar-error | Marcadores nativos de ortografia/gramática | Chromium |
::highlight(nome) | Ranges customizados registrados via JS | Chrome 105+, Safari 17.2+ |
Estilizando o Ctrl+F do navegador
::search-text é aquele que a galera pede há uma década. Antes, o highlight do "localizar na página" era intocável. Agora não mais:
/* Destaque padrão dos resultados de busca */
::search-text {
background: #ffe066;
color: #1a1a1a;
}
/* O resultado atualmente focado (aquele que o Enter pula) */
::search-text:current {
background: #ff7043;
color: #fff;
outline: 2px solid #ff7043;
}
Custom Highlight API na prática
O pulo do gato é registrar seus próprios ranges. Sem mutação de DOM, sem <mark> espalhado, sem reflow pesado em documentos grandes.
// 1. Encontre o range que você quer destacar
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
const ranges = [];
let node;
while ((node = walker.nextNode())) {
const idx = node.textContent.indexOf('pseudo-elemento');
if (idx !== -1) {
const range = new Range();
range.setStart(node, idx);
range.setEnd(node, idx + 'pseudo-elemento'.length);
ranges.push(range);
}
}
// 2. Registre tudo sob um highlight nomeado
const highlight = new Highlight(...ranges);
CSS.highlights.set('termo-busca', highlight);
/* 3. Estilize o highlight nomeado */
::highlight(termo-busca) {
background-color: #fff3a0;
text-decoration: underline wavy #e67e22;
}
Pronto. Nenhum elemento wrapper, nenhuma limpeza quando o termo de busca muda — é só dar CSS.highlights.clear() e registrar de novo.
O padrão MicroLighter
Um truque bem legal que anda circulando: construir um syntax highlighter inteiro em cima da Custom Highlight API. Em vez de emitir <span class="keyword"> pra cada token, você tokeniza o texto, monta objetos Range e registra sob highlights como keyword, string, comment. O CSS faz a coloração. O DOM fica minúsculo e trocar de tema vira praticamente de graça.

Cuidados, limites e o que ninguém te conta
Alguns pontos que os demos felizes esquecem de mencionar:
- Só um subconjunto de CSS se aplica. Pseudo-elementos de highlight aceitam pouquíssimas propriedades:
color,background-color,text-decoratione suas longhands,text-shadowe mais algumas. Você não consegue setarpadding,border,font-sizeoudisplay. Se precisa de uma caixa em volta do match, essa não é a ferramenta. - Sem
::before/::after. Não dá pra injetar conteúdo num highlight. É só estilização. - Ranges precisam estar vivos. Se o DOM mudar, seus ranges podem ficar inválidos e parar de renderizar silenciosamente. Re-registre em mutações se o conteúdo for dinâmico.
::search-texté só Chromium por enquanto. Firefox e Safari ainda não entregaram. Faça feature-detect comCSS.supports('selector(::search-text)')e tenha fallback.- Acessibilidade: highlights são puramente visuais. Leitores de tela não anunciam. Se o destaque carrega significado (tipo resultado de busca), replique em regiões
aria-liveou no nome acessível.
Receita de progressive enhancement
/* Baseline: funciona em todo lugar */
mark.search-hit {
background: #ffe066;
}
/* Upgrade: sem wrapper no DOM */
@supports selector(::highlight(termo-busca)) {
::highlight(termo-busca) {
background: #ffe066;
}
}
Manda o fallback com <mark>, empilha o highlight por cima quando suportado, e apaga o fallback quando o Baseline alcançar. E se você curte histórias de como uma primitiva nova reescreve uma stack inteira, vale ler o caso de escala do WhatsApp com Rust — mesmo padrão de "API nova destrava arquitetura mais simples".
Pra onde ir agora
- Leia a spec da CSS Custom Highlight API — é curta e legível.
- Teste
::target-textpra highlight de deep-link em sites de documentação. - Construa uma ferramentinha de anotação: pegue texto com
window.getSelection(), converta emRange, registre o highlight. Você aprende a API inteira numa tarde. - Fique de olho nas specs de
::search-texte::spelling-error— conforme chegarem em mais engines, a era do "envelopa num span" oficialmente acaba.

Fechando a conta
Pseudo-elementos de highlight do CSS são uma daquelas adições silenciosas que apagam categorias inteiras de JavaScript. Se seu codebase ainda injeta <mark> pra resultado de busca, ou carrega uma lib de 40KB só pra colorir token, a Custom Highlight API merece um olhar sério nesse trimestre.
Comece com ::highlight() pros seus próprios ranges, empilhe ::search-text e ::target-text onde houver suporte, e mantenha um fallback com <mark> pra cauda longa. O DOM que você não cria é o DOM que você não precisa limpar. 🚀