Etiqueta search en HTML: búsqueda y filtrado semánticos
La etiqueta <search> agrupa controles u otro contenido relacionado con una operación de búsqueda o filtrado. Úsala para identificar el buscador de un sitio o el filtro de una lista de lecciones: comunica la función del bloque, mientras que el formulario, el servidor o JavaScript realizan la búsqueda.
Ejemplo mínimo: buscar en un sitio
Puedes colocar este fragmento dentro del <body> de una página:
<search>
<form action="/buscar.html" method="get">
<label for="consulta">Buscar lecciones</label>
<input id="consulta" name="q" type="search">
<button type="submit">Buscar</button>
</form>
</search>
Verás una etiqueta, un campo y un botón. Al escribir html y enviar, el formulario solicitaría /buscar.html?q=html. El atributo name="q" da nombre al dato enviado; id="consulta" permite que el <label> se asocie al campo.
La ruta /buscar.html es ilustrativa: tu proyecto necesita un servidor o una implementación que lea la consulta y produzca resultados. Esta guía no crea ese destino. Para probar el envío sin un buscador real, observa la solicitud en la pestaña Red del navegador; un error 404 no significa que <search> haya fallado.
Si retiras solo <search> y </search>, el formulario sigue enviando lo mismo, pero pierde la región de búsqueda nativa. El elemento no dibuja una lupa ni genera resultados.
Sintaxis y contexto válido
Escribe apertura y cierre: <search>...</search>. Ninguna de sus etiquetas puede omitirse. Es contenido de flujo, admite contenido de flujo y puede aparecer donde este esté permitido: por ejemplo, dentro de <body>, <header> o <main>. Puede envolver un <form> o controles de filtrado local sin formulario. No lo insertes dentro de un párrafo <p>, que admite contenido de frase.
Las restricciones de los descendientes siguen aplicándose: incluir <search> dentro de un formulario no permite anidar otro <form>. Consulta el modelo de contenido de search en el estándar.
Search frente a input type search y form
Cada elemento resuelve una parte distinta:
<search>identifica la región que ofrece búsqueda o filtrado.<input type="search">proporciona el campo donde se escribe la consulta. Su aspecto y botón de borrado dependen del navegador.<form>organiza y envía los datos. No se necesita si el filtro actúa localmente mediante JavaScript.- El servidor o el script decide qué contenidos coinciden y cómo mostrar el resultado.
¿Search reemplaza a form? No. El primer ejemplo utiliza ambos porque necesita identificar la región y enviar una consulta. Cambiar el campo a type="text" tampoco elimina la semántica del contenedor, aunque deja de identificar el control como entrada de búsqueda.
Una página que solo muestra resultados pertenece al contenido principal, normalmente dentro de <main>; no envuelvas toda esa página en <search>. Sí puede haber allí una región para refinar la consulta. Las sugerencias inmediatas que forman parte del buscador también pueden incluirse en ella.
Caso práctico: filtrar tres lecciones sin servidor
Guarda el documento completo como filtro-lecciones.html, en UTF-8, y ábrelo en el navegador. Incluye todos los estilos y el script; no necesita otros archivos ni conexión. Los controles se muestran al inicializar JavaScript y la lista está escrita en HTML desde el comienzo.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Filtrar lecciones de HTML</title>
<style>
body {
max-width: 45rem;
margin: auto;
padding: 1rem;
font-family: sans-serif;
line-height: 1.6;
}
.filtro {
display: block;
padding: 1rem;
border: 1px solid #547575;
}
label { display: block; }
input, button {
box-sizing: border-box;
max-width: 100%;
font: inherit;
}
button { margin-block: 0.5rem; }
:focus-visible {
outline: 3px solid #0060a8;
outline-offset: 3px;
}
</style>
</head>
<body>
<main>
<h1>Lecciones de HTML</h1>
<search id="filtro-lecciones" class="filtro"
aria-labelledby="titulo-filtro">
<h2 id="titulo-filtro">Filtrar lecciones</h2>
<p id="ayuda-filtro">Escribe parte del nombre, con o sin acentos.</p>
<div data-controles hidden>
<label for="filtro">Nombre de la lección</label>
<input id="filtro" name="q" type="search"
aria-describedby="ayuda-filtro">
<button type="button">Mostrar todas</button>
</div>
<noscript>
<p>El filtro necesita JavaScript. Puedes leer las tres lecciones abajo.</p>
</noscript>
</search>
<p id="cantidad" role="status">3 lecciones visibles.</p>
<ul id="lecciones">
<li>Crear párrafos</li>
<li>Añadir imágenes</li>
<li>Crear enlaces</li>
</ul>
</main>
<script>
(() => {
const zona = document.getElementById("filtro-lecciones");
const campo = zona.querySelector("input");
const boton = zona.querySelector("button");
const lecciones = [...document.querySelectorAll("#lecciones > li")];
const cantidad = document.getElementById("cantidad");
const normalizar = texto => texto.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLocaleLowerCase("es").trim();
const nombres = lecciones.map(li => normalizar(li.textContent));
function filtrar() {
const consulta = normalizar(campo.value);
let visibles = 0;
lecciones.forEach((li, indice) => {
const coincide = nombres[indice].includes(consulta);
li.hidden = !coincide;
if (coincide) visibles += 1;
});
const unidad = visibles === 1 ? "lección visible" : "lecciones visibles";
cantidad.textContent = `${visibles} ${unidad}.`;
}
campo.addEventListener("input", filtrar);
boton.addEventListener("click", () => {
campo.value = "";
filtrar();
campo.focus();
});
zona.querySelector("[data-controles]").hidden = false;
filtrar();
})();
</script>
</body>
</html>
Para comprobar el resultado:
- Pulsa Tab hasta el campo. Escribe
parrafos: permanece «Crear párrafos» y el mensaje cambia a «1 lección visible.».imágeneseIMAGENESencuentran la misma lección. - Escribe
zzzz: la lista queda vacía y aparece «0 lecciones visibles.». Borra la consulta para recuperar las tres. - Con Tab, llega a «Mostrar todas» y actívalo con Enter o Espacio. Se restablece la lista y el foco vuelve al campo.
- Desactiva JavaScript y recarga. No aparecen controles inoperantes; se muestran el aviso, la cantidad inicial y las tres lecciones. Reduce la ventana para revisar lectura y foco en móvil.
El script modifica la propiedad DOM hidden de cada elemento y textContent del mensaje; no envía ni guarda datos. role="status" permite comunicar cambios sin mover el foco, aunque el anuncio concreto depende de la tecnología de asistencia.
Si sustituyes <search> por <div> conservando id, class y los descendientes, el filtro y el recuadro siguen funcionando, pero desaparece el hito de búsqueda. Retirar CSS cambia el diseño, no el filtrado ni esa semántica.
Atributos y árbol accesible
<search> no tiene atributos HTML específicos. Admite globales como id, class y lang. type, name e id del campo pertenecen al <input>; action y method, al <form>. display es una propiedad CSS, y value, una propiedad DOM del campo.
El mapeo HTML-AAM asigna al elemento el rol implícito search. No hace falta repetir role="search" en él ni en el formulario interior: podrías crear regiones anidadas redundantes. Si necesitas compatibilidad con un navegador antiguo, una alternativa es <form role="search"> para el buscador enviado, en lugar de envolverlo además en <search>.
Cuando haya varias regiones, dales nombres distintos, como «Buscar en el sitio» y «Filtrar lecciones». Usa aria-labelledby con el id de un título visible, o aria-label si no existe ese título. Estos atributos ARIA no son exclusivos de <search>. El nombre de la región no sustituye al <label> del campo. La guía de hitos search de WAI explica esa identificación.
Esta es una representación abreviada del árbol accesible inicial del ejemplo con JavaScript en Chromium:
main
heading "Lecciones de HTML" (nivel 1)
search "Filtrar lecciones"
heading "Filtrar lecciones" (nivel 2)
texto de instrucciones
searchbox "Nombre de la lección"
button "Mostrar todas"
status
"3 lecciones visibles."
list
listitem
texto "Crear párrafos"
listitem
texto "Añadir imágenes"
listitem
texto "Crear enlaces"
Compruébalo en las herramientas de accesibilidad del navegador: debe haber un hito de búsqueda, un campo con nombre y descripción, y controles nativos que mantengan el foco al filtrar. No se promete una locución idéntica en todos los lectores de pantalla.
Errores frecuentes y compatibilidad
Si no aparecen resultados, comprueba en este orden: que exista la ruta de action en una búsqueda enviada; que el campo tenga name; o, para el filtro local, que JavaScript se ejecute y que los identificadores coincidan. Envolver controles en <search> por sí solo no implementa ninguna búsqueda. Tampoco uses placeholder como única etiqueta del campo.
¿Funciona sin JavaScript? El contenedor conserva su significado. Un formulario GET puede funcionar sin scripts si existe el servidor que lo procesa. El filtro local mostrado necesita JavaScript, pero conserva todo el listado al desactivarlo.
Compatibilidad consultada el 3 de octubre de 2026: MDN y su registro Browser Compatibility Data sitúan el soporte del elemento en Chrome 118, Firefox 118 y Safari 17. La ficha incrustada en WHATWG todavía indica falta de soporte; se ha contrastado esa discrepancia con BCD. El estándar define la semántica; esa ficha no es una prueba de ejecución actual.
BCD registra el soporte del elemento, no garantiza el anuncio del hito en cada combinación de navegador y lector. HTML-AAM define el mapeo esperado; el árbol mostrado se comprobó en Chromium 153. En navegadores sin mapeo nativo puede mantenerse el formulario o el script y faltar la región accesible: revisa ambos aspectos por separado.
Para completar el formulario, consulta las guías de <form>, <input>, <label> y <button>. <main> delimita el contenido principal donde presentar los resultados.