Destacar um trecho de texto na página sempre foi um problema de DOM, não de CSS. Para pintar o termo buscado, você precisava fatiar o nó de texto e embrulhar o pedaço num <mark>, o que invalida referências de nó, atrapalha a seleção que o usuário já tinha feito e briga com qualquer biblioteca que também mexa naquela subárvore.
A CSS Custom Highlight API resolve isso: você descreve faixas de texto em JavaScript, registra num dicionário global e estiliza com ::highlight(). O DOM não muda uma vírgula. Ela é Baseline desde junho de 2025 e hoje cobre 92,02% dos acessos globais.
O que ela devolve em troca é um problema de acessibilidade, e ele não está escrito em lugar nenhum da chamada.
Os quatro passos
O fluxo tem quatro etapas e nenhuma surpresa. Você cria objetos Range, agrupa num Highlight, registra no CSS.highlights com um nome, e escreve a regra CSS para esse nome.
const faixa = new Range();
faixa.setStart(no, 10);
faixa.setEnd(no, 20);
CSS.highlights.set("resultado-busca", new Highlight(faixa));
::highlight(resultado-busca) {
background-color: #ff0066;
color: white;
}
O CSS.highlights é um objeto com interface de mapa, o HighlightRegistry. Ele tem set(), delete() e clear(). Um mesmo Highlight aceita várias faixas de uma vez, e todas herdam a mesma regra, que é o caso de uso de busca na página: você limpa o registro a cada tecla digitada e registra o conjunto novo inteiro.
A detecção de suporte é uma linha:
if (!CSS.highlights) {
// caminho alternativo
}
O que você pode estilizar, e por quê
A lista de propriedades aceitas é curta e o motivo está escrito na especificação: as permitidas são aquelas que não afetam layout e podem ser aplicadas de forma performática num ambiente altamente dinâmico.
Na prática, o que funciona é color, background-color, background-image, text-shadow e a família de text-decoration, incluindo text-decoration-color, text-decoration-line, text-decoration-style, text-decoration-thickness e text-underline-offset.
Tudo que mexe em caixa está fora: padding, border, border-radius, margin, display, transform. Não é omissão, é a razão de existir da API. O destaque pode começar no meio de um parágrafo e terminar dentro de outro elemento, atravessando fronteiras sem respeitar aninhamento. Nada que dependa de uma caixa bem definida teria significado ali.
Quem quer o efeito de etiqueta arredondada com respiro interno continua precisando de um elemento real. A API não substitui o <mark> em desenho, ela substitui em mecânica.
Sobreposição tem regra, e ela é explícita
Quando dois destaques diferentes cobrem o mesmo trecho, quem decide é o atributo priority do objeto Highlight. O padrão é 0, e o valor maior fica por cima na ordem de empilhamento. Em caso de empate, vence o registrado mais recentemente.
Dentro de um mesmo Highlight, faixas que se sobrepõem são renderizadas como união, ou seja, viram uma região sólida só. Você não vai conseguir empilhar transparência com duas faixas irmãs para escurecer a interseção: para isso, precisa de dois Highlight distintos com prioridades distintas.
Isso importa em qualquer coisa que empilhe camadas de anotação, do tipo comentário sobre revisão sobre resultado de busca. A ordem não é a de declaração no CSS, é a de prioridade no registro.
O leitor de tela não vê nada disso
Aqui está o custo. O Highlight tem um atributo type, e a especificação define exatamente três valores: "highlight", que é o padrão, "spelling-error" e "grammar-error".
Os dois últimos existem para dar semântica a erro de ortografia e de gramática, e a tecnologia assistiva pode expor isso. O padrão, "highlight", não carrega semântica nenhuma. Ele é decoração.
Se o destaque carrega informação, ele não pode existir somente em CSS. Um trecho pintado de amarelo que só existe no registro de highlights é, para quem usa leitor de tela, um trecho igual a todos os outros.
Compare com o <mark>, que é um elemento com papel definido e é anunciado. A recomendação da própria documentação é direta: para informação importante, use HTML semântico ou forneça outra pista acessível.
Em campo, isso significa que a API é excelente para o que é redundante e péssima para o que é único. Busca na página tem redundância natural: existe um campo de busca, existe um contador de ocorrências, existe navegação entre elas. O destaque visual é reforço. Já um editor que marca cláusulas de risco num contrato só com ::highlight() está escondendo a informação principal de parte dos usuários.
O padrão que resolve os dois lados
A saída prática é não escolher entre os dois. Use ::highlight() para pintar, e um canal paralelo para informar: um aria-live discreto anunciando a contagem de ocorrências, uma lista navegável dos trechos encontrados, ou o type correto quando o caso for mesmo ortografia ou gramática.
O custo de manter esse canal é baixo e ele resolve outro problema junto: quem enxerga bem, mas está com o navegador em modo de alto contraste ou com folha de estilo do usuário, também depende dele.
Onde já roda
O suporte é bom e a distribuição das datas explica por que a API demorou a aparecer em código de produção:
- Chrome e Edge: desde a versão 105.
- Safari e Safari no iOS: desde a 17.2.
- Firefox: desde a 140.
- Samsung Internet: desde a 20.
O Chrome tinha há três anos, o Firefox fechou a conta e é ele que marca o Baseline em junho de 2025. Uso global suportado hoje: 92,02%.
Os 8% restantes são o motivo para manter a checagem de CSS.highlights em vez de assumir. E o caminho alternativo aqui é confortável, porque é o que você já tinha antes: envolver com <mark>, aceitando a mexida no DOM em navegador antigo.
Perguntas frequentes
O destaque aparece no Ctrl+F do navegador?
Não. Busca nativa do navegador é outra coisa, com pseudo-elementos próprios de destaque. O ::highlight() só pinta o que você registrou por código.
As faixas sobrevivem a mudança no DOM?
Não de forma confiável. Um Range aponta para nó e deslocamento. Se o conteúdo daquele nó muda, ou o nó é substituído por uma renderização nova do framework, a faixa fica inválida ou aponta para o lugar errado. Em interface reativa, o certo é recalcular os ranges depois de cada renderização, e não guardar o objeto entre elas.
Tem custo de desempenho em documento grande?
O custo dominante é seu, não do navegador: percorrer os nós de texto com TreeWalker e montar os Range a cada tecla é o que pesa. A pintura em si foi desenhada para ser barata, e é justamente por isso que a lista de propriedades permitidas exclui tudo que forçaria novo cálculo de layout. Guarde a lista de nós de texto uma vez e refaça só as faixas.
Fontes