Ir al contenido
Evaluar mi proyecto
Componentes · NavegaciónDisponible

Barra con megamenú

Panel de tres columnas con descripciones, abierto por clic o flecha abajo; Escape y clic fuera lo cierran.

Código completo

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

nav-megamenu.tsx91 líneas
Descargar
"use client";

import { useEffect, useId, useRef, useState, type CSSProperties } from "react";

/** Barra con megamenú: un panel de tres columnas con descripciones se abre con clic, hover o flecha abajo; Escape y clic fuera lo cierran. Estilo minimalista claro. React + TypeScript, sin dependencias. */
export type NavMegamenuColumna = { title: string; items: { label: string; desc: string }[] };

export type NavMegamenuProps = {
  brand?: string;
  triggerLabel?: string;
  columns?: NavMegamenuColumna[];
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcnmm-root { box-sizing: border-box; position: relative; width: 100%; max-width: 860px; color: #18181b; font: 500 14px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
.gcnmm-root *, .gcnmm-root *::before, .gcnmm-root *::after { box-sizing: border-box; }
.gcnmm-root button, .gcnmm-root a { -webkit-tap-highlight-color: transparent; }
.gcnmm-root :focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .gcnmm-root * { animation: none !important; transition-duration: .01ms !important; } }
.gcnmm-bar { display: flex; align-items: center; gap: 6px; height: 60px; padding: 0 12px 0 20px; border: 1px solid #e4e4e7; border-radius: 16px; background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .04); }
.gcnmm-brand { margin-right: auto; font-weight: 800; font-size: 17px; letter-spacing: -.02em; }
.gcnmm-link, .gcnmm-trig { display: inline-flex; align-items: center; gap: 6px; height: 40px; padding: 0 14px; border: 0; border-radius: 10px; background: none; color: #3f3f46; text-decoration: none; cursor: pointer; font: inherit; transition: background .15s, color .15s; }
.gcnmm-link:hover, .gcnmm-trig:hover, .gcnmm-trig[aria-expanded="true"] { background: #f4f4f5; color: #18181b; }
.gcnmm-trig svg { transition: transform .25s; }
.gcnmm-trig[aria-expanded="true"] svg { transform: rotate(180deg); }
.gcnmm-cta { display: inline-flex; align-items: center; height: 40px; padding: 0 16px; border-radius: 10px; background: #18181b; color: #fff; text-decoration: none; font-weight: 600; }
.gcnmm-cta:hover { background: #3f3f46; }
.gcnmm-panel { position: absolute; z-index: 5; left: 0; right: 0; top: calc(100% + 10px); display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px 24px; padding: 20px; border: 1px solid #e4e4e7; border-radius: 18px; background: #fff; box-shadow: 0 24px 48px -16px rgba(0, 0, 0, .25); transform-origin: top center; animation: gcnmm-in .25s cubic-bezier(.16, 1, .3, 1); }
.gcnmm-col h3 { margin: 0 0 8px 10px; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: #a1a1aa; }
.gcnmm-item { display: block; padding: 10px; border-radius: 10px; color: inherit; text-decoration: none; transition: background .15s; }
.gcnmm-item:hover { background: #f4f4f5; }
.gcnmm-item b { display: block; font-weight: 600; }
.gcnmm-item span { color: #71717a; font-size: 13px; }
@keyframes gcnmm-in { from { opacity: 0; transform: translateY(-6px) scale(.98); } }
@media (max-width: 680px) { .gcnmm-panel { grid-template-columns: 1fr; } .gcnmm-link { display: none; } }`;

const DEFAULT: NavMegamenuColumna[] = [
  { title: "Construir", items: [{ label: "Software a medida", desc: "Sistemas para tu operación." }, { label: "Aplicaciones web", desc: "Paneles y portales." }, { label: "Aplicaciones móviles", desc: "Para campo y clientes." }] },
  { title: "Conectar", items: [{ label: "Integraciones", desc: "Une tus sistemas actuales." }, { label: "Automatización", desc: "Quita tareas repetitivas." }, { label: "Datos y reportes", desc: "Indicadores a la vista." }] },
  { title: "Proteger", items: [{ label: "Ciberseguridad", desc: "Revisión y buenas prácticas." }, { label: "Nube", desc: "Migración ordenada." }, { label: "Soporte", desc: "Acompañamiento continuo." }] },
];

export function NavMegamenu({ brand = "Taller", triggerLabel = "Servicios", columns = DEFAULT, className = "", style }: NavMegamenuProps) {
  const id = useId();
  const [open, setOpen] = useState(false);
  const root = useRef<HTMLDivElement>(null);
  const trig = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (!open) return;
    const away = (event: PointerEvent) => { if (!root.current?.contains(event.target as Node)) setOpen(false); };
    const esc = (event: KeyboardEvent) => { if (event.key === "Escape") { setOpen(false); trig.current?.focus(); } };
    document.addEventListener("pointerdown", away);
    document.addEventListener("keydown", esc);
    return () => { document.removeEventListener("pointerdown", away); document.removeEventListener("keydown", esc); };
  }, [open]);

  return (
    <div ref={root} className={`gcnmm-root ${className}`} style={style}>
      <style>{CSS}</style>
      <nav className="gcnmm-bar" aria-label="Principal">
        <span className="gcnmm-brand">{brand}</span>
        <button ref={trig} className="gcnmm-trig" type="button" aria-expanded={open} aria-controls={`${id}-p`} onClick={() => setOpen((value) => !value)} onKeyDown={(event) => { if (event.key === "ArrowDown") { event.preventDefault(); setOpen(true); } }}>
          {triggerLabel}
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
        </button>
        <a className="gcnmm-link" href="#" onClick={(event) => event.preventDefault()}>Proyectos</a>
        <a className="gcnmm-link" href="#" onClick={(event) => event.preventDefault()}>Nosotros</a>
        <a className="gcnmm-cta" href="#" onClick={(event) => event.preventDefault()}>Contacto</a>
      </nav>
      {open ? (
        <div id={`${id}-p`} className="gcnmm-panel">
          {columns.map((column) => (
            <div key={column.title} className="gcnmm-col">
              <h3>{column.title}</h3>
              {column.items.map((item) => (
                <a key={item.label} className="gcnmm-item" href="#" onClick={(event) => { event.preventDefault(); setOpen(false); }}>
                  <b>{item.label}</b>
                  <span>{item.desc}</span>
                </a>
              ))}
            </div>
          ))}
        </div>
      ) : null}
    </div>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

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

Ejemplo de uso de Barra con megamenú

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

ejemplo.tsx5 líneas
import { NavMegamenu } from "./nav-megamenu";

export default function Ejemplo() {
  return <NavMegamenu brand="Ejemplo" />;
}

Propiedades de NavMegamenu

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

PropiedadTipoDescripción
brand?stringOpcional.
triggerLabel?stringOpcional.
columns?NavMegamenuColumna[]Opcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 91 líneas (6 KB) y define 1 animación con @keyframes. Esto es lo que hace Barra con megamenú 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-*.
  • Responde al teclado, no solo al ratón.
  • 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)