MenĂș de navegaciĂłn
Una barra horizontal con enlaces y paneles desplegables para los encabezados de la web, los menĂșs de navegaciĂłn de documentos y las plantillas de aplicaciones, con bordes de estilo neobrutalista y sombras marcadas.
"use client"
import * as React from "react"El menĂș de navegaciĂłn es una barra horizontal de enlaces en la que cualquier elemento puede abrir un panel desplegable con mĂĄs enlaces. EstĂĄ construido sobre el componente Base UI Navigation Menu y sigue la receta neobrutalista: bordes gruesos, sombras marcadas y tipografĂa en negrita.
Recurre a este componente cuando:
- Cabeceras de sitio â enlaces de primer nivel mĂĄs paneles enriquecidos para productos, secciones de documentaciĂłn o recursos.
- NavegaciĂłn de marketing â barras de «Funciones / Precios / Recursos» donde un panel de tarjetas gana a un muro de enlaces.
- NavegaciĂłn mixta â combina elementos
NavigationMenuLinksimples con grupos desplegables en la misma barra.
InstalaciĂłn#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/navigation-menu.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/navigation-menu.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/navigation-menu.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/navigation-menu.json
Instala las siguientes dependencias:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
Copia y pega el siguiente cĂłdigo en tu proyecto.
import * as React from "react"
import { cva } from "class-variance-authority"
import { ChevronDownIcon } from "lucide-react"
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
)
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
"group flex flex-1 list-none items-center justify-center gap-0",
className
)}
{...props}
/>
)
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props}
/>
)
}
const navigationMenuTriggerStyle = cva(
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground"
)
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
)
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded group-data-[viewport=false]/navigation-menu:border-2 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:duration-300 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
className
)}
{...props}
/>
)
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div
className={cn(
"absolute top-full left-0 isolate z-50 flex justify-center"
)}
>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 md:w-(--radix-navigation-menu-viewport-width) data-open:animate-in data-open:zoom-in-90 data-closed:animate-out data-closed:zoom-out-90",
className
)}
{...props}
/>
</div>
)
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"flex items-center gap-2 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary in-data-[slot=navigation-menu-content]:rounded-sm data-active:bg-accent data-active:text-accent-foreground [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
)
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
}
Actualiza las rutas de importaciĂłn segĂșn la configuraciĂłn de tu proyecto.
Uso#
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink>Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>ComposiciĂłn#
Usa la siguiente composiciĂłn para construir un NavigationMenu:
NavigationMenu
âââ NavigationMenuList
â âââ NavigationMenuItem
â â âââ NavigationMenuTrigger
â â âââ NavigationMenuContent
â â âââ NavigationMenuLink
â â âââ NavigationMenuLink
â âââ NavigationMenuItem
â âââ NavigationMenuLink
âââ NavigationMenuIndicatorComponente de enlace#
Usa la prop render para componer un componente de enlace personalizado, como el Link de Next.js.
import Link from "next/link"
import {
NavigationMenuItem,
NavigationMenuLink,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
export function NavigationMenuDemo() {
return (
<NavigationMenuItem>
<NavigationMenuLink
render={<Link href="/docs" />}
className={navigationMenuTriggerStyle()}
>
Documentation
</NavigationMenuLink>
</NavigationMenuItem>
)
}RTL#
Para activar el soporte RTL en Neobrutalism, consulta la guĂa de configuraciĂłn de RTL.
"use client"
import * as React from "react"Accesibilidad#
A pesar del nombre, esto no es un menu de ARIA: los roles de menĂș son para comandos de aplicaciĂłn, no para la navegaciĂłn entre pĂĄginas. El componente renderiza un landmark nav de enlaces normales siguiendo el patrĂłn Disclosure Navigation de WAI-ARIA: cada disparador es un botĂłn conectado mediante aria-expanded y aria-controls con el panel que abre.
Interacciones de teclado (orientaciĂłn horizontal, la predeterminada):
| Tecla | AcciĂłn |
|---|---|
Tab / Shift + Tab | Mueve el foco por los disparadores, los enlaces y el contenido del panel abierto |
Space / Enter | Abre el panel del disparador con foco; activa el enlace enfocado |
ArrowRight / ArrowLeft | Mueve el foco al disparador o enlace siguiente / anterior |
ArrowDown | Mueve el foco desde un disparador abierto hacia su panel |
Home / End | Salta al primer / Ășltimo disparador o enlace |
Esc | Cierra el panel abierto y devuelve el foco a su disparador |
Referencia de API#
Consulta la documentaciĂłn Base UI Navigation Menu para obtener mĂĄs informaciĂłn.