A API CSS Custom Highlight permite estilizar intervalos de texto arbitrários em uma página sem modificar a estrutura do DOM. Isso permite destacar resultados de pesquisa, colorir sintaxe, usar cursores de edição colaborativa ou marcadores de erros ortográficos e gramaticais sem precisar envolver o texto em elementos extras ou depender da manipulação de innerHTML.
Implementação principal
Para destacar intervalos de texto, é necessário coletar os nós de texto de destino, criar objetos Range e Highlight, registrá-los no HighlightRegistry e estilizá-los com o pseudoelemento ::highlight().
1. Coletar nós de texto e criar intervalos
Use um TreeWalker para coletar todos os nós de texto no elemento de destino e crie objetos Range que apontem para os deslocamentos de caracteres que você quer destacar.
const article = document.querySelector("article");
// MANDATORY: Use TreeWalker to collect text nodes — do not manipulate innerHTML.
const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
allTextNodes.push(currentNode);
currentNode = treeWalker.nextNode();
}
// MANDATORY: Set range start/end on text nodes, not element nodes.
const range = new Range();
range.setStart(textNode, matchStartIndex);
range.setEnd(textNode, matchEndIndex);
Armazene em cache a lista de nós de texto e só a recrie quando o conteúdo do DOM realmente mudar, já que percorrer a árvore é uma operação cara.
2. Criar um destaque com base nos intervalos
Agrupar um ou mais objetos Range em um Highlight. Vários intervalos que compartilham o mesmo estilo pertencem a um único destaque.
const searchHighlight = new Highlight(...matchingRanges);
3. Registrar o destaque no registro
Registre cada Highlight com um nome personalizado usando CSS.highlights, que é um HighlightRegistry semelhante a Map.
// MANDATORY: Clear previous highlights before registering new ones
// to avoid stale ranges persisting on the page.
CSS.highlights.clear();
CSS.highlights.set("search-results", searchHighlight);
Quando vários destaques se sobrepõem, use a propriedade priority para controlar a ordem de empilhamento. Os destaques de maior prioridade são pintados por cima.
const primary = new Highlight(...primaryRanges);
primary.priority = 1;
const secondary = new Highlight(...secondaryRanges);
secondary.priority = 0; // painted first (behind primary)
CSS.highlights.set("primary", primary);
CSS.highlights.set("secondary", secondary);
4. Estilo com ::highlight()
Use o pseudoelemento ::highlight() no CSS para estilizar cada destaque registrado por nome.
::highlight(search-results) {
background-color: #ffdd00;
color: black;
}
Apenas um conjunto limitado de propriedades CSS funciona dentro de ::highlight(): color, background-color, text-decoration e as formas completas, text-shadow, -webkit-text-stroke-color, -webkit-text-fill-color e -webkit-text-stroke-width. Propriedades como background-image, font-size ou padding são ignoradas.
Acessibilidade
AVOID: usar destaques personalizados como substitutos do HTML semântico.
Os destaques personalizados são puramente de apresentação e não são expostos à árvore de acessibilidade. Se o texto destacado for semanticamente relevante para o documento (por exemplo, um trecho selecionado pelo usuário), use <mark>. Reserve os destaques personalizados para efeitos transitórios e apenas visuais, como resultados da pesquisa ou cores de sintaxe.
Os destaques não devem depender apenas da cor para transmitir significado. Se um destaque indicar um erro, combine-o com outro indicador visual, como text-decoration: wavy underline ou um rótulo de texto adjacente. Garanta contraste suficiente entre o fundo do destaque e a cor do texto para atender aos requisitos da WCAG 2.1 (pelo menos 4,5:1 para texto normal).
Estratégias de fallback
Para navegadores que não oferecem suporte à API CSS Custom Highlight, forneça uma experiência básica funcional em que o texto ainda seja legível, mesmo sem o destaque visual.
É possível detectar a compatibilidade antes de usar a API:
if (CSS.highlights) {
// CSS Custom Highlight API is supported.
} else {
// Fallback: wrap matches in <mark> elements.
}
Se o destaque for essencial para a experiência do usuário, use elementos <mark> para envolver o texto correspondente. Isso modifica o DOM, então tome cuidado para preservar os listeners de eventos e evitar quebrar a estrutura do documento.
if (!CSS.highlights) {
// Walk text nodes and wrap matches in <mark>, preserving structure.
const walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const nodes = [];
for (let n = walker.nextNode(); n; n = walker.nextNode()) nodes.push(n);
const term = searchTerm.toLowerCase();
for (const textNode of nodes) {
const text = textNode.textContent;
let pos = text.toLowerCase().indexOf(term);
if (pos === -1) continue;
const frag = document.createDocumentFragment();
let last = 0;
while (pos !== -1) {
frag.append(text.slice(last, pos));
const mark = document.createElement("mark");
// textContent assignment avoids HTML injection.
mark.textContent = text.slice(pos, pos + term.length);
frag.append(mark);
last = pos + term.length;
pos = text.toLowerCase().indexOf(term, last);
}
frag.append(text.slice(last));
textNode.replaceWith(frag);
}
}