A React component library that creates fluid morphing animations between a trigger element and an expanded menu. The button becomes the menu through fluid shape-shifting animation.
- Fluid Animations — Smooth spring-based morphing with customizable presets
- Directional Expansion — Menu expands in any direction with anchor alignment
- Composable API — Build exactly what you need with compound components
- Tiny Bundle — Only requires
framer-motionas a peer dependency - TypeScript First — Full type safety and IntelliSense support
npm install bloom-menu framer-motionimport { Menu } from 'bloom-menu'
import { MoreHorizontal, Pencil, Trash } from 'lucide-react'
function Example() {
return (
<Menu.Root>
<Menu.Container buttonSize={48} menuWidth={200} menuRadius={12}>
<Menu.Trigger>
<MoreHorizontal />
</Menu.Trigger>
<Menu.Content>
<Menu.Item onSelect={() => console.log('edit')}>
<Pencil /> Edit
</Menu.Item>
<Menu.Item onSelect={() => console.log('delete')}>
<Trash /> Delete
</Menu.Item>
</Menu.Content>
</Menu.Container>
</Menu.Root>
)
}Control where the menu expands from and how it aligns to the trigger:
<Menu.Root direction="top" anchor="start"> {/* Expands up, aligned to start */}
<Menu.Root direction="bottom" anchor="center"> {/* Expands down, centered */}
<Menu.Root direction="left" anchor="end"> {/* Expands left, aligned to end */}<Menu.Root animation="snappy"> {/* Quick ease-out curve */}
<Menu.Root animation="gentle"> {/* Default, smooth spring */}
<Menu.Root animation="relaxed"> {/* Slower, laid-back spring */}
{/* Or use custom spring config */}
<Menu.Root animation={{ stiffness: 400, damping: 25 }}>The root component that provides context for all other components.
| Prop | Type | Default | Description |
|---|---|---|---|
open |
boolean |
— | Controlled open state |
onOpenChange |
(open: boolean) => void |
— | Callback when open state changes |
defaultOpen |
boolean |
false |
Default open state (uncontrolled) |
direction |
"top" | "bottom" | "left" | "right" |
"top" |
Menu expansion direction |
anchor |
"start" | "center" | "end" |
"start" |
Anchor alignment |
animation |
"snappy" | "gentle" | "relaxed" | SpringConfig |
"gentle" |
Animation preset |
closeOnClickOutside |
boolean |
true |
Close when clicking outside |
closeOnEscape |
boolean |
true |
Close when pressing Escape |
The morphing element that animates between button and menu states.
| Prop | Type | Default | Description |
|---|---|---|---|
buttonSize |
number | { width, height } |
40 |
Closed button size (number for square, object for rectangular) |
menuWidth |
number |
200 |
Open menu width |
menuRadius |
number |
24 |
Open menu border-radius |
Content shown when closed. Fades out when the menu opens.
| Prop | Type | Default | Description |
|---|---|---|---|
className |
string |
— | Additional class names |
style |
CSSProperties |
— | Additional styles |
Menu content shown when open. Fades in with a slight delay.
| Prop | Type | Default | Description |
|---|---|---|---|
className |
string |
— | Additional class names |
style |
CSSProperties |
— | Additional styles |
Individual menu items with hover highlighting.
| Prop | Type | Default | Description |
|---|---|---|---|
onSelect |
() => void |
— | Called when selected |
disabled |
boolean |
false |
Disable the item |
closeOnSelect |
boolean |
true |
Close menu after selection |
Renders content in a portal.
| Prop | Type | Default | Description |
|---|---|---|---|
container |
HTMLElement |
document.body |
Portal target |
Optional backdrop overlay.
| Prop | Type | Default | Description |
|---|---|---|---|
className |
string |
— | Additional class names |
onClick |
() => void |
closes menu | Click handler |
Bloom includes the following accessibility features:
- Proper
role="menu"androle="menuitem"attributes aria-expandedandaria-haspopupon triggeraria-disabledon disabled items- Respects
prefers-reduced-motionmedia query - Click outside and Escape key to close
MIT