Primitives
Menu
Five looks, one API. The highlight slides to the chosen item; a folded branch that holds it lights up instead.
soft · Default — settings, panels
line · Long lists, docs, drawers
raised · Side panels on a page
gradient · The main menu of a page
dark · Command areas, over media
menu.tsx
"use client";
import { Badge, Menu, MenuGroup, MenuItem, MenuSub, type MenuLook } from "@waveinno/ui";
import { Car, DoorOpen, LayoutDashboard, Plus, Settings, Users, Wallet } from "lucide-react";
const LOOKS: { look: MenuLook; when: string }[] = [
{ look: "soft", when: "Default — settings, panels" },
{ look: "line", when: "Long lists, docs, drawers" },
{ look: "raised", when: "Side panels on a page" },
{ look: "gradient", when: "The main menu of a page" },
{ look: "dark", when: "Command areas, over media" },
];
export default function LooksDemo() {
return (
<div className="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
{LOOKS.map(({ look, when }) => (
<div key={look}>
<p className="mb-2 text-[11.5px] text-muted">
<span className="font-semibold text-text">{look}</span> · {when}
</p>
<Menu variant={look} defaultValue="sessions" aria-label={`${look} menu`} className="max-w-[260px]">
<MenuGroup label="Workspace" action={<Plus className="size-3.5 text-muted" aria-hidden />}>
<MenuItem value="overview" icon={<LayoutDashboard />}>
Overview
</MenuItem>
<MenuItem value="sessions" icon={<Car />} count={12}>
Live sessions
</MenuItem>
<MenuItem value="gates" icon={<DoorOpen />} shortcut="G">
Gates
</MenuItem>
<MenuSub label="Payments" icon={<Wallet />}>
<MenuItem value="cash">Cash shifts</MenuItem>
<MenuItem value="bkash">bKash</MenuItem>
<MenuItem
value="payouts"
badge={
<Badge variant="gradient" dot={false}>
New
</Badge>
}
>
Payouts
</MenuItem>
</MenuSub>
</MenuGroup>
<MenuGroup label="Account">
<MenuItem value="team" icon={<Users />}>
Team
</MenuItem>
<MenuItem value="settings" icon={<Settings />} shortcut="⌘,">
Settings
</MenuItem>
</MenuGroup>
</Menu>
</div>
))}
</div>
);
}Import#
Everything comes from the package root. The example above uses:
your-component.tsx
import { Badge, Menu, MenuGroup, MenuItem, MenuSub, type MenuLook } from "@waveinno/ui";Props#
Read from the source of this version, so they match the code exactly.
<Menu>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
variant | "soft" | "line" | "raised" | "gradient" | "dark" | "panel" | "rail" | "soft" | |
value | string | — | The chosen item's `value` (controlled). |
defaultValue | string | — | |
onValueChange | (value: string) => void | — | |
heading | React.ReactNode | — | On top of the list: a title, or in the rail, the product mark. |
collapsible | boolean | — | A collapse toggle in the heading row. |
collapsed | boolean | — | |
defaultCollapsed | boolean | false | |
onCollapsedChange | (collapsed: boolean) => void | — | |
collapsedMode | "icons" | "hidden" | "icons" | `icons`: narrow to the icons. `hidden`: fold away entirely (toggle it from outside). |
collapseShortcut | boolean | false | ⌘B / Ctrl+B toggles it. Off by default: AppShell already uses ⌘B for its panel. |
Also takes Omit<React.HTMLAttributes<HTMLElement>, "defaultValue" | "onChange">.
<MenuGroup>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
label | React.ReactNode | — | |
action | React.ReactNode | — | Something small on the label's right: a "+" button, a count. |
className | string | — | |
childrenrequired | React.ReactNode | — |
<MenuItem>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | |
description | React.ReactNode | — | Second line, muted: what is behind the item. Puts the icon in a tile. |
badge | React.ReactNode | — | |
count | number | string | — | A number on the right: unread, pending, items inside. |
shortcut | string | — | |
value | string | — | Chosen when it matches Menu's `value`; clicking makes it the value. |
active | boolean | — | Mark it chosen yourself, e.g. from the router's pathname. Overrides `value`. |
danger | boolean | — | |
asChild | boolean | — | Render your own element (a Next.js Link): `<MenuItem asChild><Link href="/x">Label</Link></MenuItem>`. |
Also takes Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "value">.
<MenuSub>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | |
description | React.ReactNode | — | Second line, muted: what is behind the item. Puts the icon in a tile. |
badge | React.ReactNode | — | |
count | number | string | — | A number on the right: unread, pending, items inside. |
shortcut | string | — | |
labelrequired | React.ReactNode | — | |
defaultOpen | boolean | false | |
open | boolean | — | |
onOpenChange | (open: boolean) => void | — | |
disabled | boolean | — | |
className | string | — | |
childrenrequired | React.ReactNode | — |
<Badge>
src/components/ui/badge.tsx| Prop | Type | Default | Description |
|---|---|---|---|
dot | boolean | true | |
variant | "soft" | "solid" | "outline" | "dot" | "gradient" | "live" | "soft" |
Also takes HTMLAttributes<HTMLSpanElement> & VariantProps<typeof badgeVariants>.
Install#
If the project does not have the package yet, add it, then follow the setup for Next.js or Vite once.
$ npm install @waveinno/ui