Menu
Menu suspenso com uma lista de ações ou opções, aberto por um botão — o equivalente ao DropdownMenu do shadcn.
Última ação: —
'use client';
import { Copy, Ellipsis, FileText, Pencil, Trash2 } from 'lucide-react';
import { useState } from 'react';
import type { Key } from 'react-aria-components';
import { Button } from '@/components/ui/button';
import { Menu, MenuContent, MenuItem, MenuSeparator, MenuShortcut } from '@/components/ui/menu';
export default function MenuDemo() {
const [ultima, setUltima] = useState<Key | null>(null);
return (
<div className="flex flex-col items-center gap-3">
<Menu>
<Button variant="outline" size="icon" aria-label="Ações do cliente">
<Ellipsis />
</Button>
<MenuContent aria-label="Ações do cliente" onAction={setUltima}>
<MenuItem id="editar" textValue="Editar">
<Pencil /> Editar <MenuShortcut>Ctrl+E</MenuShortcut>
</MenuItem>
<MenuItem id="duplicar" textValue="Duplicar">
<Copy /> Duplicar <MenuShortcut>Ctrl+D</MenuShortcut>
</MenuItem>
<MenuItem id="orcamento" textValue="Gerar orçamento">
<FileText /> Gerar orçamento
</MenuItem>
<MenuSeparator />
<MenuItem id="excluir" variant="destructive" textValue="Excluir">
<Trash2 /> Excluir
</MenuItem>
</MenuContent>
</Menu>
<p className="text-sm text-muted-foreground">Última ação: {ultima ?? '—'}</p>
</div>
);
}Instalação
npx shadcn@latest add @lenstech/menuUso
import { Button } from "@/components/ui/button"
import { Menu, MenuContent, MenuItem, MenuSeparator } from "@/components/ui/menu"<Menu>
<Button variant="outline">Ações</Button>
<MenuContent aria-label="Ações" onAction={(key) => executar(key)}>
<MenuItem id="editar">Editar</MenuItem>
<MenuItem id="duplicar">Duplicar</MenuItem>
<MenuSeparator />
<MenuItem id="excluir" variant="destructive">Excluir</MenuItem>
</MenuContent>
</Menu>Vindo do shadcn? DropdownMenu vira Menu
DropdownMenu → Menu, DropdownMenuContent → MenuContent, DropdownMenuItem → MenuItem,
DropdownMenuLabel → MenuHeader, DropdownMenuSub → SubmenuTrigger. Não há DropdownMenuTrigger: o
primeiro filho de Menu é o botão que abre o menu.
Use onAction, não onClick
Os itens respondem a mouse, toque, teclado (Enter/Espaço) e busca por digitação. Trate a escolha com
onAction — no MenuContent (recebe o id do item) ou no próprio MenuItem. Para navegar, passe href ao item.
Itens com conteúdo além de texto (ícone, atalho) precisam de textValue para a busca por digitação.
Exemplos
Seções e links
Agrupe itens com MenuSection + MenuHeader e separe com MenuSeparator. Itens com href viram links reais;
isDisabled desabilita um item.
'use client';
import { ChevronDown, FileText, LogOut, Settings, User } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Menu,
MenuContent,
MenuHeader,
MenuItem,
MenuSection,
MenuSeparator,
MenuShortcut,
} from '@/components/ui/menu';
export default function MenuSections() {
return (
<Menu>
<Button variant="outline">
Dra. Marina Lopes <ChevronDown />
</Button>
<MenuContent aria-label="Minha conta" popoverClassName="w-56" placement="bottom end">
<MenuSection>
<MenuHeader>Minha conta</MenuHeader>
<MenuItem href="#perfil" textValue="Perfil">
<User /> Perfil
</MenuItem>
<MenuItem href="#configuracoes" textValue="Configurações">
<Settings /> Configurações <MenuShortcut>Ctrl+,</MenuShortcut>
</MenuItem>
<MenuItem isDisabled textValue="Faturas">
<FileText /> Faturas (em breve)
</MenuItem>
</MenuSection>
<MenuSeparator />
<MenuItem textValue="Sair" onAction={() => console.log('sair')}>
<LogOut /> Sair
</MenuItem>
</MenuContent>
</Menu>
);
}Seleção (checkbox e radio)
Defina selectionMode numa MenuSection (ou no MenuContent): "multiple" mostra um check, "single" mostra
uma bolinha. Controle com selectedKeys e onSelectionChange, como nos equivalentes DropdownMenuCheckboxItem e
DropdownMenuRadioGroup do shadcn.
Colunas: nome, telefone · Ordem: recentes
'use client';
import { Columns3 } from 'lucide-react';
import { useState } from 'react';
import type { Selection } from 'react-aria-components';
import { Button } from '@/components/ui/button';
import { Menu, MenuContent, MenuHeader, MenuItem, MenuSection, MenuSeparator } from '@/components/ui/menu';
export default function MenuSelection() {
const [colunas, setColunas] = useState<Selection>(new Set(['nome', 'telefone']));
const [ordem, setOrdem] = useState<Selection>(new Set(['recentes']));
return (
<div className="flex flex-col items-center gap-3">
<Menu>
<Button variant="outline">
<Columns3 /> Exibição
</Button>
<MenuContent aria-label="Opções de exibição" popoverClassName="w-56">
<MenuSection selectionMode="multiple" selectedKeys={colunas} onSelectionChange={setColunas}>
<MenuHeader inset>Colunas visíveis</MenuHeader>
<MenuItem id="nome">Nome</MenuItem>
<MenuItem id="telefone">Telefone</MenuItem>
<MenuItem id="email">E-mail</MenuItem>
<MenuItem id="cidade">Cidade</MenuItem>
</MenuSection>
<MenuSeparator />
<MenuSection
selectionMode="single"
selectedKeys={ordem}
onSelectionChange={setOrdem}
disallowEmptySelection
>
<MenuHeader inset>Ordenar por</MenuHeader>
<MenuItem id="recentes">Mais recentes</MenuItem>
<MenuItem id="az">Nome (A–Z)</MenuItem>
<MenuItem id="valor">Maior valor</MenuItem>
</MenuSection>
</MenuContent>
</Menu>
<p className="text-sm text-muted-foreground">
Colunas: {colunas === 'all' ? 'todas' : [...colunas].join(', ')} · Ordem: {[...ordem].join('')}
</p>
</div>
);
}Submenu
SubmenuTrigger recebe um MenuItem (o gatilho) e um MenuContent (o submenu). Abre com hover, seta → ou Enter.
'use client';
import { FileText, Mail, MessageCircle, Pencil, Share2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Menu, MenuContent, MenuItem, SubmenuTrigger } from '@/components/ui/menu';
export default function MenuSubmenu() {
return (
<Menu>
<Button variant="outline">Orçamento #318</Button>
<MenuContent aria-label="Ações do orçamento" onAction={(key) => console.log('ação', key)}>
<MenuItem id="editar" textValue="Editar">
<Pencil /> Editar
</MenuItem>
<SubmenuTrigger>
<MenuItem id="enviar" textValue="Enviar orçamento">
<Share2 /> Enviar orçamento
</MenuItem>
<MenuContent aria-label="Enviar por">
<MenuItem id="email" textValue="Por e-mail">
<Mail /> Por e-mail
</MenuItem>
<MenuItem id="whatsapp" textValue="Por WhatsApp">
<MessageCircle /> Por WhatsApp
</MenuItem>
<MenuItem id="pdf" textValue="Baixar PDF">
<FileText /> Baixar PDF
</MenuItem>
</MenuContent>
</SubmenuTrigger>
</MenuContent>
</Menu>
);
}Quando usar
- Use para agrupar ações secundárias: o "⋯" de uma linha de tabela, o menu da conta do usuário.
- Não use para escolher um valor de formulário — use
Select. - Não use para a navegação principal do site — use links visíveis ou um
Sheetno mobile. - Se há só uma ou duas ações, prefira botões visíveis a escondê-las num menu.
API
Menu
É o MenuTrigger do React Aria (também exportado como MenuTrigger). Aceita
isOpen, defaultOpen, onOpenChange e trigger ("press" ou "longPress").
MenuContent
Aceita todas as props do Menu do React Aria, mais:
Prop
Type
MenuItem
Aceita todas as props do MenuItem do React Aria, mais:
Prop
Type
Demais partes
Prop
Type