Ir al contenido
Evaluar mi proyecto
Componentes · BotonesDisponible

Botón Aqua de gel

Cápsula skeuomórfica de gel con brillo especular, bisel y pulso en el botón principal, como los botones de Mac OS X.

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

boton-aqua.tsx56 líneas
Descargar
"use client";

import type { CSSProperties, ReactNode } from "react";

/** Botón skeuomórfico estilo Aqua: cápsula de gel con brillo especular y pulso en el botón principal. React + TypeScript, sin dependencias. */
export type BotonAquaTono = "azul" | "grafito" | "verde" | "rojo";

export type BotonAquaProps = {
  children?: ReactNode;
  tone?: BotonAquaTono;
  /** El botón principal respira con un pulso suave, como en Mac OS X. */
  pulse?: boolean;
  type?: "button" | "submit";
  disabled?: boolean;
  onClick?: () => void;
  className?: string;
  style?: CSSProperties;
};

const TONES: Record<BotonAquaTono, [string, string, string]> = {
  azul: ["#8cc8ff", "#1f7fe6", "#0a4fc0"],
  grafito: ["#a9b0bd", "#626b7b", "#363c49"],
  verde: ["#98e08a", "#2c9f27", "#176b1a"],
  rojo: ["#ffa99a", "#dc3a24", "#9a1a0c"],
};

const CSS = `
.gcaqu-root { box-sizing: border-box; display: inline-block; max-width: 100%; font: 400 14px/1.3 "Lucida Grande", "Helvetica Neue", ui-sans-serif, system-ui, sans-serif; }
.gcaqu-root *, .gcaqu-root *::before, .gcaqu-root *::after { box-sizing: border-box; }
.gcaqu-root button { font: inherit; -webkit-tap-highlight-color: transparent; }
.gcaqu-root :focus-visible { outline: 3px solid rgba(70, 150, 255, .7); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .gcaqu-root *, .gcaqu-root *::before, .gcaqu-root *::after { animation: none !important; transition-duration: .01ms !important; } }
.gcaqu-btn { position: relative; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 30px; border: 1px solid var(--gcaqu-edge, #0b5ed7); border-radius: 999px; cursor: pointer; font-size: 15px; font-weight: 600; color: #fff; text-shadow: 0 -1px 1px rgba(0, 0, 0, .55), 0 0 2px rgba(0, 0, 0, .25);
  background: linear-gradient(180deg, var(--gcaqu-a) 0%, var(--gcaqu-b) 52%, var(--gcaqu-c) 100%);
  box-shadow: inset 0 -10px 14px -6px rgba(255, 255, 255, .45), inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 0 rgba(255, 255, 255, .6), 0 6px 12px -4px rgba(0, 20, 60, .45); transition: filter .15s, transform .15s; }
.gcaqu-btn::before { content: ""; position: absolute; left: 6%; right: 6%; top: 2px; height: 46%; border-radius: 999px 999px 40% 40% / 999px 999px 70% 70%; background: linear-gradient(180deg, rgba(255, 255, 255, .88), rgba(255, 255, 255, .22)); pointer-events: none; }
.gcaqu-btn:hover:not(:disabled) { filter: brightness(1.07) saturate(1.1); }
.gcaqu-btn:active:not(:disabled) { filter: brightness(.88); transform: translateY(1px); box-shadow: inset 0 3px 8px rgba(0, 20, 60, .5), 0 1px 0 rgba(255, 255, 255, .6); }
.gcaqu-btn[data-pulse="true"] { animation: gcaqu-pulse 1.7s ease-in-out infinite; }
.gcaqu-btn:disabled { filter: grayscale(1) opacity(.55); cursor: not-allowed; animation: none; }
.gcaqu-label { position: relative; }
@keyframes gcaqu-pulse { 50% { filter: brightness(1.14) saturate(1.2); box-shadow: inset 0 -10px 14px -6px rgba(255, 255, 255, .6), inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 0 rgba(255, 255, 255, .6), 0 0 16px 2px rgba(60, 150, 255, .55); } }`;

export function BotonAqua({ children = "Continuar", tone = "azul", pulse = false, type = "button", disabled, onClick, className = "", style }: BotonAquaProps) {
  const [a, b, c] = TONES[tone];
  const vars = { "--gcaqu-a": a, "--gcaqu-b": b, "--gcaqu-c": c, "--gcaqu-edge": c } as CSSProperties;
  return (
    <span className={`gcaqu-root ${className}`} style={{ ...vars, ...style }}>
      <style>{CSS}</style>
      <button className="gcaqu-btn" type={type} disabled={disabled} onClick={onClick} data-pulse={pulse ? "true" : undefined}>
        <span className="gcaqu-label">{children}</span>
      </button>
    </span>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/boton-aqua

Ejemplo de uso de Botón Aqua de gel

Después de copiar boton-aqua.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { BotonAqua } from "./boton-aqua";

export default function Ejemplo() {
  return <BotonAqua tone="azul" pulse>Contenido</BotonAqua>;
}

Propiedades de BotonAqua

BotonAqua acepta 8 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
children?ReactNodeOpcional.
tone?BotonAquaTonoOpcional.
pulse?booleanEl botón principal respira con un pulso suave, como en Mac OS X.
type?"button" | "submit"Opcional.
disabled?booleanOpcional.
onClick?() => voidOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Variantes disponibles

  • tone: azul, grafito, verde, rojo
  • type: button, submit

Personaliza Botón Aqua de gel 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.

VariableValor por defecto
--gcaqu-edge#0b5ed7

Accesibilidad y rendimiento

El archivo tiene 56 líneas (4 KB) y define 1 animación con @keyframes. Esto es lo que hace Botón Aqua de gel 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).
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)