Botón de copiar con confirmación
Comando seleccionable con copia real al portapapeles, confirmación y recuperación si el navegador bloquea la acción.
npm install mi-proyectoCódigo completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useState, useEffect, useRef, type CSSProperties } from "react";
/** Botón de copiar con confirmación. React + TypeScript, sin dependencias. Personaliza con variables --gcxc-*. */
export type BotonCopiarProps = {
text?: string;
label?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcxc-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcxc-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcxc-root *, .gcxc-root *::before, .gcxc-root *::after { box-sizing: border-box; }
.gcxc-root button, .gcxc-root input, .gcxc-root textarea, .gcxc-root select { font: inherit; }
.gcxc-root button, .gcxc-root a { -webkit-tap-highlight-color: transparent; }
.gcxc-root :focus-visible { outline: 2px solid var(--gcxc-accent, #25724b); outline-offset: 4px; }
.gcxc-root ::selection { background: var(--gcxc-accent, #25724b); color: #fff; }
.gcxc-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcxc-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcxc-root *, .gcxc-root *::before, .gcxc-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcxc-root { position: relative; display: flex; align-items: center; gap: 20px; padding: 12px 14px 12px 22px; border: 1px solid #d0d7d2; border-radius: 12px; background: var(--gcxc-bg, #fff); flex-wrap: wrap; }
.gcxc-root code { font: 13px/1.5 ui-monospace, monospace; overflow-wrap: anywhere; flex: 1; }
.gcxc-root button { display: flex; align-items: center; gap: 8px; min-height: 44px; border: 0; border-radius: 8px; padding: 0 14px; color: #24533c; background: #e9f2ec; cursor: pointer; transition: background .2s; }
.gcxc-root button:hover { background: #d6e8dc; }
.gcxc-root button[data-done="true"] { background: #24533c; color: #fff; }
.gcxc-status { position: absolute; top: calc(100% + 10px); left: 0; font-size: 12px; max-width: 100%; }`;
export function BotonCopiar({
text = "npm install mi-proyecto",
label = "Copiar comando",
className = "",
style,
}: BotonCopiarProps) {
const [state, setState] = useState<"idle" | "copied" | "error">("idle");
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(
() => () => {
if (timer.current) clearTimeout(timer.current);
},
[],
);
async function copy() {
try {
await navigator.clipboard.writeText(text);
setState("copied");
} catch {
setState("error");
}
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setState("idle"), 2400);
}
return (
<div className={`gcxc-root ${className}`} style={style}>
<style>{CSS}</style>
<code>{text}</code>
<button
type="button"
onClick={copy}
aria-label={label}
data-done={state === "copied"}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.7"
aria-hidden="true"
>
{state === "copied" ? (
<path d="m5 12 4 4 10-10" />
) : (
<>
<rect x="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8V4H4v12h4" />
</>
)}
</svg>
<span>{state === "copied" ? "Copiado" : "Copiar"}</span>
</button>
<span className="gcxc-status" role="status">
{state === "error"
? "No se pudo copiar. Selecciona el texto y cópialo manualmente."
: state === "copied"
? "Comando copiado al portapapeles."
: ""}
</span>
</div>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/boton-copiarEjemplo de uso de Botón de copiar con confirmación
Después de copiar boton-copiar.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { BotonCopiar } from "./boton-copiar";
export default function Ejemplo() {
return <BotonCopiar text="Ejemplo" />;
}Propiedades de BotonCopiar
BotonCopiar acepta 4 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
text? | string | Opcional. |
label? | string | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Botón de copiar con confirmación con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--gcxc-fg | #202421 |
--gcxc-accent | #25724b |
--gcxc-bg | #fff |
Accesibilidad y rendimiento
El archivo tiene 94 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Botón de copiar con confirmación por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
- Declara roles ARIA para lectores de pantalla: status.
- Es un componente de cliente de Next.js: incluye la directiva "use client".