Botón de barrido
Botón de contorno que se rellena con una línea que baja y voltea el texto en el mismo borde.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
/**
* Botón de contorno fino que se rellena con una línea de barrido: al pasar el puntero (o
* enfocarlo con el teclado) una línea baja de arriba a abajo y detrás de ella el botón se
* llena y el texto se invierte justo en la línea, no antes. Sin brillos ni resplandor.
* El color se sobrescribe con --gcsl-tone y el del texto invertido con --gcsl-on.
*
* <BotonScanline label="Iniciar revisión" href="#revision" tone="signal" />
*/
import type { CSSProperties, MouseEventHandler } from "react";
export type BotonScanlineProps = {
label?: string;
href?: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
tone?: "bone" | "signal" | "azure" | "citron";
className?: string;
style?: CSSProperties;
};
const TONES: Record<string, string> = {
bone: "#ece9e2",
signal: "#ff6a3d",
azure: "#6cb0ff",
citron: "#d6e04b",
};
const CSS = `
.gcsl-root { --gcsl-h: 46px; position: relative; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; height: var(--gcsl-h); padding: 0 24px; border: 1px solid var(--gcsl-ink, #ece9e2); border-radius: 4px; background: transparent; color: var(--gcsl-ink, #ece9e2); font: 500 14px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; letter-spacing: .01em; text-decoration: none; white-space: nowrap; cursor: pointer; overflow: hidden; }
.gcsl-fill { position: absolute; inset: 0; overflow: hidden; background: var(--gcsl-ink, #ece9e2); color: var(--gcsl-on, #0a0a0b); transform: translateY(-100%); transition: transform .5s cubic-bezier(.65, 0, .35, 1); }
.gcsl-fill::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: color-mix(in srgb, var(--gcsl-ink, #ece9e2) 35%, #fff); }
.gcsl-fill > span { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; transform: translateY(100%); transition: transform .5s cubic-bezier(.65, 0, .35, 1); }
.gcsl-root:hover .gcsl-fill, .gcsl-root:focus-visible .gcsl-fill, .gcsl-root:hover .gcsl-fill > span, .gcsl-root:focus-visible .gcsl-fill > span { transform: none; }
.gcsl-root:active { transform: translateY(1px); }
.gcsl-root:focus-visible { outline: 2px solid var(--gcsl-ink, #ece9e2); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .gcsl-fill, .gcsl-fill > span { transition: none; } }
`;
export function BotonScanline({
label = "Iniciar revisión",
href,
onClick,
tone = "bone",
className,
style,
}: BotonScanlineProps) {
const props = {
className: ["gcsl-root", className].filter(Boolean).join(" "),
style: { ...style, ["--gcsl-ink" as string]: `var(--gcsl-tone, ${TONES[tone] ?? TONES.bone})` },
};
const content = (
<>
<span>{label}</span>
<span className="gcsl-fill" aria-hidden="true"><span>{label}</span></span>
</>
);
return (
<>
<style>{CSS}</style>
{href
? <a {...props} href={href}>{content}</a>
: <button {...props} type="button" onClick={onClick}>{content}</button>}
</>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/boton-scanlineEjemplo de uso de Botón de barrido
Después de copiar boton-scanline.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { BotonScanline } from "./boton-scanline";
export default function Ejemplo() {
return <BotonScanline tone="bone" label="Ejemplo" />;
}Propiedades de BotonScanline
BotonScanline acepta 6 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
label? | string | Opcional. |
href? | string | Opcional. |
onClick? | MouseEventHandler<HTMLButtonElement> | Opcional. |
tone? | "bone" | "signal" | "azure" | "citron" | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Variantes disponibles
tone:bone,signal,azure,citron
Personaliza Botón de barrido 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 |
|---|---|
--gcsl-ink | #ece9e2 |
--gcsl-on | #0a0a0b |
--gcsl-tone | ${TONES[tone] ?? TONES.bone} |
Accesibilidad y rendimiento
El archivo tiene 67 líneas (3 KB) y no necesita @keyframes. Esto es lo que hace Botón de barrido 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).
- Expone su estado a tecnologías de apoyo con atributos aria-*.
- Calcula tonos con color-mix(), así que un solo color base alcanza.
Acerca del producto
Al pasar el puntero o enfocarlo, una línea baja y detrás de ella el botón se rellena; el texto invertido aparece exactamente en la línea. Sin brillos.
Incluye
Un solo archivo React + TypeScript con estilos dentro. Sin Tailwind ni dependencias externas.
- Texto invertido recortado con el relleno
- Funciona con foco de teclado
- Cuatro tonos
- Botón o enlace según las props
- Respeta prefers-reduced-motion