¡Hola Devs! Esto sí me emociona
¿Ya te tocó querer resaltar el resultado de una búsqueda en la página y la única salida era inyectar <span> o <mark> por todos lados? Pues esa era ya terminó. 🎉
La CSS Custom Highlight API llegó justo para eso. Junto con pseudo-elementos como ::search-text, ::highlight(), ::target-text, ::spelling-error y ::grammar-error, ahora puedes estilizar rangos de texto que nunca fueron envueltos en un elemento — con puro CSS.
Esto cambia el juego para buscadores internos, modos de lectura, editores colaborativos y cualquier cosa que necesite marcar texto sin tocar el DOM. Si alguna vez escribiste un wrapper sobre mark.js solo para colorear un resultado, respira: hay camino mejor. Y si quieres ver cómo la plataforma viene evolucionando en esta dirección, checa esta guía sobre CSS highlight pseudo-elements.

Los pseudo-elementos que necesitas conocer
Aquí va el mapa, del más estable al más experimental:
| Pseudo-elemento | Para qué sirve | Soporte |
|---|---|---|
::selection | Texto seleccionado por el usuario | Universal |
::target-text | Texto al que apunta un fragmento de URL | Chrome, Safari |
::search-text | Coincidencias del "buscar en la página" | Chromium |
::spelling-error / ::grammar-error | Marcadores nativos de ortografía/gramática | Chromium |
::highlight(nombre) | Rangos personalizados registrados vía JS | Chrome 105+, Safari 17.2+ |
Estilizando el Ctrl+F del navegador
::search-text es el que la banda lleva pidiendo una década. Antes, el resaltado del "buscar en la página" era intocable. Ya no:
/* Resaltado por defecto de las coincidencias */
::search-text {
background: #ffe066;
color: #1a1a1a;
}
/* La coincidencia activa (a la que salta el Enter) */
::search-text:current {
background: #ff7043;
color: #fff;
outline: 2px solid #ff7043;
}
Custom Highlight API en la vida real
El truco bueno es registrar tus propios rangos. Sin mutar el DOM, sin <mark> por todos lados, sin reflows pesados en documentos grandes.
// 1. Encuentra el rango que quieres resaltar
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. Regístralos bajo un highlight con nombre
const highlight = new Highlight(...ranges);
CSS.highlights.set('termino-busqueda', highlight);
/* 3. Estiliza el highlight con nombre */
::highlight(termino-busqueda) {
background-color: #fff3a0;
text-decoration: underline wavy #e67e22;
}
Listo. Cero elementos wrapper, cero limpieza cuando cambia el término de búsqueda — nomás haces CSS.highlights.clear() y registras de nuevo.
El patrón MicroLighter
Un truco bien chido que anda circulando: armar un syntax highlighter completo sobre la Custom Highlight API. En lugar de emitir <span class="keyword"> por cada token, tokenizas el texto, armas objetos Range y los registras bajo highlights como keyword, string, comment. El CSS se encarga del color. El DOM queda chiquito y cambiar de tema sale casi gratis.

Cuidados, límites y lo que nadie te cuenta
Unos puntos que los demos felices se olvidan de mencionar:
- Solo aplica un subconjunto de CSS. Los pseudo-elementos de highlight aceptan muy pocas propiedades:
color,background-color,text-decorationy sus longhands,text-shadowy algunas más. No puedes setearpadding,border,font-sizenidisplay. Si necesitas una caja alrededor del match, esta no es la herramienta. - Sin
::before/::after. No puedes inyectar contenido en un highlight. Es solo estilizado. - Los rangos deben estar vivos. Si el DOM cambia, tus rangos pueden quedar inválidos y dejar de renderizar en silencio. Re-registra en mutaciones si el contenido es dinámico.
::search-textes solo Chromium por ahora. Firefox y Safari no lo han liberado. Haz feature-detect conCSS.supports('selector(::search-text)')y ten fallback.- Accesibilidad: los highlights son puramente visuales. Los lectores de pantalla no los anuncian. Si el resaltado comunica algo (tipo resultado de búsqueda), replícalo en regiones
aria-liveo en el nombre accesible.
Receta de progressive enhancement
/* Baseline: funciona en todos lados */
mark.search-hit {
background: #ffe066;
}
/* Upgrade: sin wrapper en el DOM */
@supports selector(::highlight(termino-busqueda)) {
::highlight(termino-busqueda) {
background: #ffe066;
}
}
Manda el fallback con <mark>, encima le pones el highlight cuando haya soporte, y borras el fallback cuando Baseline lo alcance. Y si te laten las historias de cómo una primitiva nueva reescribe toda una stack, échale un ojo al caso de escala de WhatsApp con Rust — mismo patrón de "API nueva destraba arquitectura más simple".
¿Y ahora pa' dónde?
- Lee la spec de la CSS Custom Highlight API — está corta y se lee bien.
- Prueba
::target-textpara resaltar deep-links en sitios de documentación. - Ármate una herramientita de anotaciones: toma texto con
window.getSelection(), conviértelo aRange, registra el highlight. Aprendes toda la API en una tarde. - Échale ojo a las specs de
::search-texty::spelling-error— conforme lleguen a más engines, la era del "envuélvelo en un span" se acaba oficialmente.

Cerrando cuentas
Los pseudo-elementos de highlight en CSS son de esas adiciones silenciosas que borran categorías enteras de JavaScript. Si tu codebase todavía inyecta <mark> para resultados de búsqueda, o carga una librería de 40KB solo para colorear tokens, la Custom Highlight API merece una mirada seria este trimestre.
Empieza con ::highlight() para tus propios rangos, encima pon ::search-text y ::target-text donde haya soporte, y mantén un fallback con <mark> para la cola larga. El DOM que no creas es el DOM que no tienes que limpiar. 🚀
Conclusión
Adoptar la Custom Highlight API no es solo una mejora de performance — es un cambio de mentalidad: dejas de tratar el texto como estructura y empiezas a tratarlo como dato. Ese es el tipo de salto que separa al frontend que sobrevive del que se atora. Dale, prueba con un buscador interno esta semana y verás.