Menu
A popup list of actions or options opened from a trigger.
Basic
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@makeplane/propel/components/menu";
import { Copy, ExternalLink, Pencil, Trash2 } from "lucide-react";
export default function BasicDemo() {
return (
<Menu>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="md"
render={<MenuTrigger />}
label="Actions"
/>
<MenuContent sizing="sm">
<MenuItem icon={<Icon icon={Pencil} tint="secondary" />} label="Edit" />
<MenuItem icon={<Icon icon={Copy} tint="secondary" />} label="Make a copy" />
<MenuItem icon={<Icon icon={ExternalLink} tint="secondary" />} label="Open in new tab" />
<MenuSeparator />
<MenuItem tone="danger" icon={<Icon icon={Trash2} />} label="Delete" />
</MenuContent>
</Menu>
);
}Checkbox items
Multi-select rows built from MenuCheckboxItem. Each row keeps its own checked state and the menu stays open on click.
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Menu,
MenuCheckboxItem,
MenuContent,
MenuSearch,
MenuTrigger,
} from "@makeplane/propel/components/menu";
import * as React from "react";
const PROPERTIES = [
{ key: "assignee", label: "Assignee" },
{ key: "priority", label: "Priority" },
{ key: "due_date", label: "Due date" },
{ key: "labels", label: "Labels" },
{ key: "start_date", label: "Start date" },
] as const;
export default function CheckboxItemsDemo() {
const [checked, setChecked] = React.useState<Record<string, boolean>>({
assignee: true,
priority: true,
});
const [query, setQuery] = React.useState("");
const visible = PROPERTIES.filter((p) => p.label.toLowerCase().includes(query.toLowerCase()));
return (
<Menu>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="md"
render={<MenuTrigger />}
label="Display properties"
/>
<MenuContent
sizing="sm"
search={<MenuSearch value={query} onValueChange={setQuery} placeholder="Search" />}
>
{visible.map((p) => (
<MenuCheckboxItem
key={p.key}
checked={Boolean(checked[p.key])}
onCheckedChange={(next) => setChecked((c) => ({ ...c, [p.key]: next }))}
label={p.label}
/>
))}
</MenuContent>
</Menu>
);
}Radio group
Single-select rows wrapped in a MenuRadioGroup sharing one value.
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Menu,
MenuContent,
MenuRadioGroup,
MenuRadioItem,
MenuTrigger,
} from "@makeplane/propel/components/menu";
import * as React from "react";
export default function RadioGroupDemo() {
const [density, setDensity] = React.useState("comfortable");
return (
<Menu>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="md"
render={<MenuTrigger />}
label="Density"
/>
<MenuContent sizing="sm">
<MenuRadioGroup value={density} onValueChange={setDensity}>
<MenuRadioItem value="comfortable" closeOnClick={false} label="Comfortable" />
<MenuRadioItem value="compact" closeOnClick={false} label="Compact" />
</MenuRadioGroup>
</MenuContent>
</Menu>
);
}With description
A description gives each row a muted second line; passing it switches the row to a taller, top-aligned layout.
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import { Menu, MenuContent, MenuItem, MenuTrigger } from "@makeplane/propel/components/menu";
import { Globe, Lock } from "lucide-react";
import * as React from "react";
export default function WithDescriptionDemo() {
const [selected, setSelected] = React.useState("private");
return (
<Menu>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="md"
render={<MenuTrigger />}
label="Visibility"
/>
<MenuContent sizing="lg">
<MenuItem
icon={<Icon icon={Lock} tint="secondary" />}
description="Accessible only by invite"
selected={selected === "private"}
closeOnClick={false}
onClick={() => setSelected("private")}
label="Private"
/>
<MenuItem
icon={<Icon icon={Globe} tint="secondary" />}
description="Anyone in the workspace except Guests can join"
selected={selected === "public"}
closeOnClick={false}
onClick={() => setSelected("public")}
label="Public"
/>
</MenuContent>
</Menu>
);
}Submenu
MenuSubmenu nests a MenuSubmenuContent behind a MenuSubmenuTrigger row.
Show codeHide code
import { Badge } from "@makeplane/propel/components/badge";
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import {
Menu,
MenuContent,
MenuItem,
MenuSubmenu,
MenuSubmenuContent,
MenuSubmenuTrigger,
MenuTrigger,
} from "@makeplane/propel/components/menu";
import {
Circle,
CircleCheck,
CircleDashed,
CircleDot,
CircleX,
SignalHigh,
SignalLow,
SignalMedium,
} from "lucide-react";
export default function SubmenuDemo() {
return (
<Menu>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="md"
render={<MenuTrigger />}
label="Filter by"
/>
<MenuContent sizing="sm">
<MenuSubmenu>
<MenuSubmenuTrigger
endContent={<Badge magnitude="sm" tone="neutral" label="4" />}
label="Priority"
/>
<MenuSubmenuContent sizing="sm">
<MenuItem
icon={<Icon icon={SignalHigh} tint="secondary" />}
closeOnClick={false}
label="Urgent"
/>
<MenuItem
icon={<Icon icon={SignalHigh} tint="secondary" />}
closeOnClick={false}
label="High"
/>
<MenuItem
icon={<Icon icon={SignalMedium} tint="secondary" />}
closeOnClick={false}
label="Medium"
/>
<MenuItem
icon={<Icon icon={SignalLow} tint="secondary" />}
closeOnClick={false}
label="Low"
/>
</MenuSubmenuContent>
</MenuSubmenu>
<MenuSubmenu>
<MenuSubmenuTrigger
endContent={<Badge magnitude="sm" tone="neutral" label="5" />}
label="State"
/>
<MenuSubmenuContent sizing="sm">
<MenuItem
icon={<Icon icon={CircleDashed} tint="secondary" />}
closeOnClick={false}
label="Backlog"
/>
<MenuItem
icon={<Icon icon={Circle} tint="secondary" />}
closeOnClick={false}
label="Todo"
/>
<MenuItem
icon={<Icon icon={CircleDot} tint="secondary" />}
closeOnClick={false}
label="In progress"
/>
<MenuItem
icon={<Icon icon={CircleCheck} tint="secondary" />}
closeOnClick={false}
label="Done"
/>
<MenuItem
icon={<Icon icon={CircleX} tint="secondary" />}
closeOnClick={false}
label="Cancelled"
/>
</MenuSubmenuContent>
</MenuSubmenu>
</MenuContent>
</Menu>
);
}Installation
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@makeplane/propel/components/menu";
Props
Menu
| Prop | Type | Required | Description |
|---|---|---|---|
| open | boolean | No | Whether the menu is open. Controlled; pair with `onOpenChange`. |
| defaultOpen | boolean | No | Whether the menu is open on mount. Uncontrolled. |
| onOpenChange | ((open: boolean, eventDetails: MenuRootChangeEventDetails) => void) | No | Called with the next open state when the menu opens or closes. |
| modal | boolean | No | Modal behavior while open. |
| children | ReactNode | PayloadChildRenderFunction<Payload> | No | The trigger and menu surface (`MenuTrigger`, `MenuContent`). |
MenuContent
| Prop | Type | Required | Description |
|---|---|---|---|
| render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuPopupState> | No | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| side | "top" | "bottom" | "left" | "right" | "inline-end" | "inline-start" | No | Which side of the trigger the menu opens toward. |
| sideOffset | number | OffsetFunction | No | Distance in px between the trigger and the menu. |
| align | "center" | "start" | "end" | No | Alignment of the menu relative to the trigger along `side`. |
| sizing | "auto" | "anchor" | "sm" | "md" | "lg" | No | How the menu popup sizes itself. |
| search | ReactNode | No | Sticky chrome pinned above the role="menu" popup. |
| footer | ReactNode | No | Sticky chrome pinned below the role="menu" popup. |
MenuItem
| Prop | Type | Required | Description |
|---|---|---|---|
| render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuItemState> | No | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| tone | "neutral" | "accent" | "danger" | No | Row text palette. |
| icon | ReactNode | No | Leading element before the label, e.g. `<Icon icon={Settings} tint="secondary" />`. |
| label | string | Yes | Primary row label. |
| description | string | No | Muted secondary line under the label. |
| secondaryText | string | No | Muted text shown inline after the label. |
| endContent | ReactElement<unknown, string | JSXElementConstructor<any>> | No | Element rendered at the inline end of the row, e.g. `<Shortcut keys="⌘ K" />`. |
| selected | boolean | No | Single-select selected state. |