Notion Floating Action Menu
A faithful Notion-inspired quick-create menu with a compact action panel and an animated compose-to-close interaction.
Installation
Pro components require registry authentication. Add your Unlumen UI Pro key as UNLUMEN_LICENSE_KEY in your .env.local file and follow the setup guide.
File Structure
notion-floating-action-menu.tsx
Usage
import { FileText, MessageCircle, Mic, Table2 } from "lucide-react";
import {
NotionFloatingActionMenu,
type NotionFloatingActionMenuItem,
} from "@/components/unlumen-ui/notion-floating-action-menu";
const actions: NotionFloatingActionMenuItem[] = [
{ value: "page", label: "Page", icon: <FileText /> },
{ value: "discussion", label: "Chat", icon: <MessageCircle /> },
{ value: "ai-notes", label: "AI Meeting Notes", icon: <Mic /> },
{ value: "database", label: "Database", icon: <Table2 /> },
];
export default function Example() {
return (
<NotionFloatingActionMenu
items={actions}
onItemSelect={(item) => console.log(item.value)}
/>
);
}API Reference
NotionFloatingActionMenu
| Prop | Type | Default | Description |
|---|---|---|---|
items | NotionFloatingActionMenuItem[] | - | Actions rendered in the floating menu. |
open? | boolean | - | Controlled open state of the menu. |
defaultOpen? | boolean | false | Initial open state when the component is uncontrolled. |
onOpenChange? | (open: boolean) => void | - | Called whenever the menu opens or closes. |
onItemSelect? | (item: NotionFloatingActionMenuItem) => void | - | Called after an enabled action is selected. |
align? | "start" | "end" | "start" | Aligns the menu with the start or end edge of the trigger. |
menuOffset? | number | 8 | Gap in pixels between the trigger and menu. |
menuWidth? | number | 180 | Width of the expanded menu in pixels. |
closeOnSelect? | boolean | true | Closes the menu after an enabled action is selected. |
triggerLabel? | string | "Open quick actions" | Accessible label used while the menu is closed. |
closeLabel? | string | "Close quick actions" | Accessible label used while the menu is open. |
triggerIcon? | React.ReactNode | - | Custom icon displayed while the menu is closed. |
closeIcon? | React.ReactNode | - | Custom icon displayed while the menu is open. |
springStiffness? | number | 520 | Spring stiffness used by the panel, actions, and trigger. |
springDamping? | number | 40 | Spring damping used by the panel, actions, and trigger. |
menuClassName? | string | - | Additional classes applied to the floating menu panel. |
itemClassName? | string | - | Additional classes applied to every action. |
className? | string | - | Additional classes applied to the root element. |
NotionFloatingActionMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Unique value identifying the action. |
label | string | - | Visible action label. |
icon | React.ReactNode | - | Icon rendered before the label. |
href? | string | - | Optional link destination. Items without one render as buttons. |
disabled? | boolean | false | Prevents selection and exposes the disabled state. |
onSelect? | (item: NotionFloatingActionMenuItem) => void | - | Callback scoped to this action. |
target? | React.HTMLAttributeAnchorTarget | - | Anchor target used when href is provided. |
rel? | string | - | Anchor relationship used when href is provided. |
Notes
- The panel opens with a short, restrained spring and staggered action reveal, echoing Notion's sidebar quick-create menu.
- Escape closes the menu and restores focus to the trigger. Arrow keys, Home, and End move between enabled actions.
- Clicking outside closes the menu. Reduced-motion preferences preserve every state change without scale, travel, blur, or stagger.
- Use the controlled
openprop when the menu state needs to follow application state.
Credits
Built by leo.
The interaction is inspired by Notion's sidebar quick-create menu and SwiftUI's zoom-style transitions.
Keep in mind
Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.