Creación de un Botón de Copiar para Bloques de Código
Crea un botón de copiar para bloques de código con la API Clipboard, usando textContent, try/catch, feedback visual y aria-labels.
Si alguna vez has visto a alguien luchar por seleccionar y copiar manualmente un fragmento de código —o lo has experimentado tú mismo— ya sabes por qué un botón de copiar para bloques de código es importante. Es un pequeño detalle de la interfaz de usuario que marca una diferencia real, especialmente para usuarios que navegan con teclado, comandos de voz o pantallas táctiles.
Este artículo te guía a través de la construcción de una interfaz limpia y confiable para copiar fragmentos de código utilizando la moderna Clipboard API, sin necesidad de bibliotecas.
Puntos Clave
- Usa
navigator.clipboard.writeText()para una implementación de copia moderna y basada en promesas que requiere un contexto seguro (HTTPS o localhost) y un evento iniciado por el usuario. - Extrae el código con
textContent, no coninnerHTML, para evitar copiar los envoltorios<span>del resaltador de sintaxis junto con el código real. - Envuelve la llamada en un bloque
try/catchpara manejar errores de permisos y proporcionar retroalimentación visual clara a los usuarios. - Mejora la accesibilidad agregando un
aria-labelal botón, especialmente cuando los iconos reemplazan a las etiquetas de texto. - El obsoleto
document.execCommand('copy')solo debe usarse como último recurso de respaldo para entornos heredados.
Cómo Funciona el Método writeText de la Clipboard API
El enfoque moderno para copiar al portapapeles en JavaScript es navigator.clipboard.writeText(). Está basado en promesas, es asíncrono y compatible con todos los navegadores actuales.
Dos cosas que debes saber antes de usarlo:
- Requiere un contexto seguro. La Clipboard API solo funciona sobre HTTPS. En
localhost, el HTTP simple también se trata como seguro, por lo que está bien para desarrollo. - Debe activarse mediante una acción del usuario. Llamarlo dentro de un manejador de eventos
clickcumple este requisito automáticamente.
await navigator.clipboard.writeText("your text here");
Eso es el núcleo de toda la funcionalidad.
Extracción de Texto de un Bloque de Código
Tu HTML probablemente se ve algo así:
<pre><code>const greeting = "hello";</code></pre>
Para obtener el texto sin formato, usa textContent, no innerHTML. Usar innerHTML copiaría las etiquetas HTML junto con el código, lo cual nunca es lo que deseas.
const code = document.querySelector("pre code").textContent;
Si tus bloques de código utilizan un resaltador de sintaxis como Prism.js o Highlight.js, el resaltador envuelve los tokens en elementos <span>. textContent elimina todo eso y devuelve solo el texto plano, que es exactamente lo que debe llegar al portapapeles del usuario.
Discover how at OpenReplay.com.
Construcción del Botón de Copiar para Bloques de Código
Aquí tienes una implementación completa y funcional:
document.querySelectorAll("pre").forEach((block) => {
const button = document.createElement("button");
button.type = "button";
button.textContent = "Copy";
button.setAttribute("aria-label", "Copy code to clipboard");
button.addEventListener("click", async () => {
const code = block.querySelector("code")?.textContent ?? "";
try {
await navigator.clipboard.writeText(code);
button.textContent = "Copied!";
setTimeout(() => (button.textContent = "Copy"), 2000);
} catch (err) {
console.error("Copy failed:", err);
button.textContent = "Failed";
setTimeout(() => (button.textContent = "Copy"), 2000);
}
});
block.style.position = "relative";
block.appendChild(button);
});
Algunas cosas que vale la pena mencionar aquí:
aria-labelproporciona al botón un nombre accesible para los lectores de pantalla, lo cual es especialmente importante si más adelante reemplazas el texto por un icono.e.currentTargetes más seguro quee.targetcuando tu botón contiene elementos hijos como un icono SVG, ya quee.targetpuede apuntar al icono en sí en lugar del botón. (Puedes acceder a él agregando el parámetroevental manejador de clic si es necesario.)- El bloque
try/catchmaneja los rechazos de permisos o fallos inesperados de manera elegante, brindando a los usuarios retroalimentación visible en lugar de un error silencioso.
¿Y los Navegadores Antiguos?
navigator.clipboard tiene un excelente soporte en los navegadores modernos. Si necesitas dar soporte a entornos más antiguos, existe document.execCommand('copy') como alternativa, pero está obsoleto y es poco confiable. Úsalo solo como último recurso, detrás de una verificación de funcionalidad:
if (!navigator.clipboard) {
// legacy fallback using document.execCommand('copy')
}
Para la mayoría de los sitios de documentación y herramientas para desarrolladores construidos hoy en día, puedes confiar tranquilamente solo en la Clipboard API.
Conclusión
Un botón de copiar funcional para bloques de código se reduce a tres cosas: obtener el texto con textContent, escribirlo con navigator.clipboard.writeText() y proporcionar al usuario una retroalimentación clara sobre el éxito o el fallo. Mantén el botón accesible con un aria-label, maneja los errores de manera explícita y tendrás una implementación lista para producción en menos de 30 líneas de JavaScript puro.
Preguntas Frecuentes
¿Por qué no funciona la Clipboard API en mi servidor de desarrollo local?
La Clipboard API requiere un contexto seguro, lo que significa HTTPS en producción. Sin embargo, los navegadores tratan localhost como seguro por defecto, por lo que el HTTP simple funciona bien durante el desarrollo. Si estás probando en una IP de red local como 192.168.x.x, la API fallará porque eso no se considera seguro. Usa localhost o configura un servidor de desarrollo HTTPS en su lugar.
¿Puedo copiar texto sin que un usuario haga clic en un botón?
No. La Clipboard API requiere activación del usuario, lo que significa que la llamada debe ocurrir dentro de un manejador de eventos activado por el usuario, como click, keydown o pointerup. Llamar a writeText al cargar la página o dentro de un setTimeout fallará silenciosamente o lanzará un error de permisos. Esta restricción previene que sitios maliciosos secuestren el portapapeles sin consentimiento.
¿Cómo copio el código preservando los saltos de línea y la sangría?
La propiedad textContent ya preserva los espacios en blanco, incluidos los saltos de línea y la sangría, exactamente como están escritos en tu HTML. Siempre que tus elementos pre y code contengan código fuente correctamente formateado, el texto copiado coincidirá. Evita usar innerText, que puede normalizar los espacios en blanco según el renderizado CSS y producir resultados inconsistentes entre navegadores.
¿Cuál es la diferencia entre e.target y e.currentTarget en el manejador de clic?
e.target se refiere al elemento que realmente recibió el clic, que podría ser un elemento hijo como un icono SVG dentro de tu botón. e.currentTarget siempre se refiere al elemento al que se adjuntó el escuchador de eventos, en este caso el botón en sí. Usar currentTarget previene errores cuando los usuarios hacen clic en iconos o spans anidados dentro del botón.