Barra dei menu
Una barra orizzontale con menu a tendina per barre degli strumenti, editor e dashboard in stile app — con quel tocco neobrutalista fatto di bordi e ombre.
import {
Menubar,
MenubarCheckboxItem,La menubar è una fila orizzontale persistente di trigger — File, Modifica, Visualizza — ognuno dei quali apre il proprio menu a discesa di comandi, voci checkbox, gruppi radio e sottomenu. Costruita sul componente Base UI Menubar e rifinita secondo la ricetta neobrutalista: bordi spessi, ombre nette e tipografia in grassetto.
Quando usarlo:
- Editor nel browser — strumenti per documenti, design o codice che richiedono l’intera superficie di comandi File/Modifica/Visualizza.
- Dashboard di amministrazione — azioni in blocco, viste da commutare e comandi di esportazione in un’unica barra sempre visibile.
- Web app in stile desktop — client di posta, fogli di calcolo, file manager che imitano l’interfaccia nativa.
Installazione#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/menubar.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/menubar.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/menubar.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/menubar.json
Installa le seguenti dipendenze:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
Copia e incolla il seguente codice nel tuo progetto.
"use client"
import * as React from "react"
import { CheckIcon, ChevronRightIcon } from "lucide-react"
import { Menubar as MenubarPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Menubar({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
return (
<MenubarPrimitive.Root
data-slot="menubar"
className={cn(
"flex h-8 items-center gap-0.5 rounded border-2 bg-background p-[3px] shadow-md",
className
)}
{...props}
/>
)
}
function MenubarMenu({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
}
function MenubarGroup({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
}
function MenubarPortal({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
}
function MenubarRadioGroup({
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return (
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
)
}
function MenubarTrigger({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
return (
<MenubarPrimitive.Trigger
data-slot="menubar-trigger"
className={cn(
"flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-none select-none hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-accent-foreground",
className
)}
{...props}
/>
)
}
function MenubarContent({
className,
align = "start",
alignOffset = -4,
sideOffset = 8,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
return (
<MenubarPortal>
<MenubarPrimitive.Content
data-slot="menubar-content"
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn("z-50 min-w-36 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded border-2 bg-popover p-1 text-popover-foreground shadow-md duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95", className )}
{...props}
/>
</MenubarPortal>
)
}
function MenubarItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<MenubarPrimitive.Item
data-slot="menubar-item"
data-inset={inset}
data-variant={variant}
className={cn(
"group/menubar-item relative flex cursor-default items-center gap-1.5 rounded-sm px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!",
className
)}
{...props}
/>
)
}
function MenubarCheckboxItem({
className,
children,
checked,
inset,
...props
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.CheckboxItem
data-slot="menubar-checkbox-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-sm py-1.5 pr-2 pl-7 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
<MenubarPrimitive.ItemIndicator>
<CheckIcon />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
)
}
function MenubarRadioItem({
className,
children,
inset,
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.RadioItem
data-slot="menubar-radio-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-sm py-1.5 pr-2 pl-7 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
<MenubarPrimitive.ItemIndicator>
<CheckIcon />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
)
}
function MenubarLabel({
className,
inset,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.Label
data-slot="menubar-label"
data-inset={inset}
className={cn(
"px-1.5 py-1 text-sm font-medium data-inset:pl-7",
className
)}
{...props}
/>
)
}
function MenubarSeparator({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
return (
<MenubarPrimitive.Separator
data-slot="menubar-separator"
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
)
}
function MenubarShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="menubar-shortcut"
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground",
className
)}
{...props}
/>
)
}
function MenubarSub({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
}
function MenubarSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.SubTrigger
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn(
"flex cursor-default items-center gap-1.5 rounded-sm px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="cn-rtl-flip ml-auto size-4" />
</MenubarPrimitive.SubTrigger>
)
}
function MenubarSubContent({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
return (
<MenubarPrimitive.SubContent
data-slot="menubar-sub-content"
className={cn("z-50 min-w-32 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded border-2 bg-popover p-1 text-popover-foreground shadow-md duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
{...props}
/>
)
}
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
}
Aggiorna i percorsi di importazione in base alla configurazione del tuo progetto.
Utilizzo#
import {
Menubar,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from "@/components/ui/menubar"<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>New Window</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>Share</MenubarItem>
<MenubarItem>Print</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>Composizione#
Usa la seguente composizione per costruire un Menubar:
Menubar
├── MenubarMenu
│ ├── MenubarTrigger
│ └── MenubarContent
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarItem
│ │ └── MenubarItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarCheckboxItem
│ │ └── MenubarCheckboxItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ └── MenubarRadioGroup
│ │ ├── MenubarRadioItem
│ │ └── MenubarRadioItem
│ └── MenubarSub
│ ├── MenubarSubTrigger
│ └── MenubarSubContent
│ └── MenubarGroup
│ ├── MenubarLabel
│ ├── MenubarItem
│ └── MenubarItem
└── MenubarMenu
├── MenubarTrigger
└── MenubarContent
└── MenubarGroup
├── MenubarLabel
├── MenubarItem
└── MenubarItemEsempi#
Checkbox#
Usa MenubarCheckboxItem per le opzioni attivabili e disattivabili.
import {
Menubar,
MenubarCheckboxItem,Radio#
Usa MenubarRadioGroup e MenubarRadioItem per le opzioni a selezione singola.
"use client"
import * as React from "react"Sottomenu#
Usa MenubarSub, MenubarSubTrigger e MenubarSubContent per i menu annidati.
import {
Menubar,
MenubarContent,Con icone#
import {
FileIcon,
FolderIcon,RTL#
Per abilitare il supporto RTL in Neobrutalism, consulta la guida alla configurazione RTL.
"use client"
import * as React from "react"Accessibilità#
La menubar segue il pattern WAI-ARIA Menubar: Base UI rende role="menubar" con semantica menu/menuitem e gestisce il focus con un tabindex mobile, quindi l’intera barra è un unico punto di tabulazione.
Interazioni da tastiera:
| Tasto | Azione |
|---|---|
Tab / Shift + Tab | Sposta il focus dentro e fuori dalla menubar (un unico punto per l’intera barra) |
Space / Enter | Apre il menu del trigger con il focus; attiva la voce con il focus |
ArrowRight / ArrowLeft | Passa da un trigger all’altro; a menu aperto, cambia menu oppure entra / esce da un sottomenu |
ArrowDown / ArrowUp | Apre il menu da un trigger; sposta il focus tra le voci |
Esc | Chiude il menu aperto e riporta il focus sul suo trigger |
| Carattere stampabile | Sposta il focus sulla voce successiva che inizia con quel carattere (typeahead) |
I tasti freccia orizzontali si invertono automaticamente in RTL.
Riferimento API#
Consulta la documentazione Base UI Menubar.