import { Button, Flex, HStack, Menu, Portal } from '@fidely-ui/react'
import { CiGrid42, CiSettings, CiViewList } from 'react-icons/ci'
import { MdKeyboardCommandKey } from 'react-icons/md'
export const MenuBasics = () => {
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="outline">
Open menu <Menu.Indicator />
</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content minW="160px">
<Menu.ItemGroup>
<Menu.ItemGroupLabel>Workspace</Menu.ItemGroupLabel>
<Menu.Separator />
<Menu.Item value="dashboard">
<Flex justify="space-between" align="center" width="full">
<HStack>
<CiGrid42 /> Dashboard
</HStack>
<HStack gap="0">
<MdKeyboardCommandKey /> D
</HStack>
</Flex>
</Menu.Item>
<Menu.Item value="projects">
<HStack>
<CiViewList /> Projects
</HStack>
</Menu.Item>
<Menu.Item value="preferences">
<Flex justify="space-between" align="center" width="full">
<HStack>
<CiSettings /> Preferences
</HStack>
<HStack gap="0">
<MdKeyboardCommandKey /> P
</HStack>
</Flex>
</Menu.Item>
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
Usage
import { Menu } from '@fidely-ui/react'<Menu.Root>
<Menu.Trigger />
<Menu.Positioner>
<Menu.Content>
<Menu.Item />
<Menu.ItemGroup>
<Menu.Item />
</Menu.ItemGroup>
<Menu.Separator />
<Menu.Arrow>
<Menu.ArrowTip />
</Menu.Arrow>
<Menu.CheckboxItem>✓</Menu.CheckboxItem>
<Menu.RadioItemGroup>
<Menu.RadioItem>✓</Menu.RadioItem>
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>Examples
Context menu
Use the Menu.ContextTrigger component to create a context menu.
import { Center, Menu, Portal } from '@fidely-ui/react'
export const MenuContext = () => {
return (
<Menu.Root>
<Menu.ContextTrigger width="full">
<Center
height="40"
userSelect="none"
borderWidth="2px"
borderStyle="dashed"
rounded="lg"
padding="4"
>
Right-click to open menu
</Center>
</Menu.ContextTrigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="refresh">Refresh</Menu.Item>
<Menu.Item value="duplicate">Duplicate</Menu.Item>
<Menu.Item value="rename">Rename</Menu.Item>
<Menu.Separator />
<Menu.Item value="share">Share</Menu.Item>
<Menu.Item
value="delete"
color="fg.error"
_hover={{ bg: 'red.2', color: 'fg.error' }}
>
Delete...
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
Menu Group
Use the Menu.ItemGroup component to group related menu items.
import { Button, Menu, Portal } from '@fidely-ui/react'
export const MenuGroup = () => {
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="outline">Options</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>Appearance</Menu.ItemGroupLabel>
<Menu.Item value="light">Light mode</Menu.Item>
<Menu.Item value="dark">Dark mode</Menu.Item>
</Menu.ItemGroup>
<Menu.Separator />
<Menu.ItemGroup>
<Menu.ItemGroupLabel>Layout</Menu.ItemGroupLabel>
<Menu.Item value="compact">Compact</Menu.Item>
<Menu.Item value="comfortable">Comfortable</Menu.Item>
<Menu.Item value="spacious">Spacious</Menu.Item>
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
Submenu
Here's an example of how to create a submenu.
import { Button, Menu, Portal } from '@fidely-ui/react'
import { LuChevronRight } from 'react-icons/lu'
export const MenuSubmenu = () => {
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="subtle">Open menu</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="edit">Edit</Menu.Item>
{/* First Submenu */}
<Menu.Root positioning={{ placement: 'right-start' }}>
<Menu.TriggerItem>
View <LuChevronRight />
</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="revenue">Revenue</Menu.Item>
<Menu.Item value="transactions">Transactions</Menu.Item>
<Menu.Item value="sales">Sales</Menu.Item>
{/* Second Submenu */}
<Menu.Root
positioning={{ placement: 'right-start', gutter: 9 }}
>
<Menu.TriggerItem>
Analytics <LuChevronRight />
</Menu.TriggerItem>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="daily">Daily</Menu.Item>
<Menu.Item value="weekly">Weekly</Menu.Item>
<Menu.Item value="monthly">Monthly</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
<Menu.Separator />
<Menu.Item
value="delete"
color="fg.error"
_hover={{ bg: 'red.2', color: 'fg.error' }}
>
Delete…
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
Danger Item
Here's an example of how to style a menu item that is used to delete an item.
import { Button, Menu, Portal } from '@fidely-ui/react'
export const MenuDangerItem = () => {
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="outline" size="sm">
Open Menu
</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="rename">Rename</Menu.Item>
<Menu.Item value="export">Export</Menu.Item>
<Menu.Item
value="delete"
color="fg.error"
_hover={{ bg: 'red.2', color: 'fg.error' }}
>
Delete...
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
Menu with Avatar
Here's an example that composes the Menu with the Avatar component to display a menu underneath an avatar.
import { Avatar, Menu, Portal } from '@fidely-ui/react'
export const MenuAvatar = () => {
return (
<Menu.Root positioning={{ placement: 'right-end' }}>
<Menu.Trigger
rounded="full"
focusRing="outside"
focusRingColor="orange.9"
>
<Avatar.Root>
<Avatar.Fallback name="Justice Chimobi" />
<Avatar.Image src="/justice-chimobi.jpeg" />
</Avatar.Root>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="profile">View profile</Menu.Item>
<Menu.Item value="preferences">Preferences</Menu.Item>
<Menu.Item value="appearance">Appearance</Menu.Item>
<Menu.Separator />
<Menu.Item value="help">Help & support</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
With Checkbox Items
Here's an example of how to create a menu with checkbox items.
'use client'
import { Button, Menu, Portal, useCheckboxGroup } from '@fidely-ui/react'
import { CiSettings } from 'react-icons/ci'
import { FaCheck } from 'react-icons/fa6'
export const MenuCheckbox = () => {
const group = useCheckboxGroup({ defaultValue: ['line-numbers'] })
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="outline" size="sm">
<CiSettings /> Editor options
</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>Editor options</Menu.ItemGroupLabel>
{items.map(({ label, value }) => (
<Menu.CheckboxItem
key={value}
value={value}
checked={group.isChecked(value)}
onCheckedChange={() => group.toggleValue(value)}
>
{label}
<Menu.ItemIndicator>
<FaCheck />
</Menu.ItemIndicator>
</Menu.CheckboxItem>
))}
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
const items = [
{ label: 'Line numbers', value: 'line-numbers' },
{ label: 'Word wrap', value: 'word-wrap' },
{ label: 'Minimap', value: 'minimap' },
]
With Radio Items
Here's an example of how to create a menu with radio items.
'use client'
import { useState } from 'react'
import { Button, Menu, Portal } from '@fidely-ui/react'
import { FaSortAmountUp } from 'react-icons/fa'
import { FaCheck } from 'react-icons/fa6'
export const MenuRadio = () => {
const [value, setValue] = useState('recent')
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="outline" size="sm">
<FaSortAmountUp /> Sort by
</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.RadioItemGroup
value={value}
onValueChange={(e) => setValue(e.value)}
>
{items.map((item) => (
<Menu.RadioItem key={item.value} value={item.value}>
{item.label}
<Menu.ItemIndicator>
<FaCheck />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
const items = [
{ label: 'Most recent', value: 'recent' },
{ label: 'Oldest first', value: 'oldest' },
{ label: 'Alphabetical', value: 'alphabetical' },
]
Placement
Use the positioning.placement prop to control the placement of the menu.
import { Menu } from '@fidely-ui/react/menu'
const Demo = () => {
return (
<Menu.Root positioning={{ placement: 'right-end' }}>{/* ... */}</Menu.Root>
)
}Menu with links
Here's an example of how to style a menu item that is used to delete an item.
import { Button, Flex, Menu, Portal } from '@fidely-ui/react'
import { GoLinkExternal } from 'react-icons/go'
export const MenuWithLinks = () => {
return (
<Menu.Root>
<Menu.Trigger asChild>
<Button variant="outline">Select UI libraries</Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
<Menu.Item value="fidely-ui" asChild>
<a
href="https://fidely-ui.vercel.app/"
target="_blank"
rel="noopener noreferrer"
>
<Flex justify="space-between" align="center" width="full">
Fidely UI
<GoLinkExternal />
</Flex>
</a>
</Menu.Item>
<Menu.Item value="chakra-ui" asChild>
<a
href="https://chakra-ui.com/"
target="_blank"
rel="noopener noreferrer"
>
<Flex justify="space-between" align="center" width="full">
Chakra UI
<GoLinkExternal />
</Flex>
</a>
</Menu.Item>
<Menu.Item value="mui" asChild>
<a
href="https://mui.com/material-ui/"
target="_blank"
rel="noopener noreferrer"
>
<Flex justify="space-between" align="center" width="full">
MUI
<GoLinkExternal />
</Flex>
</a>
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>
)
}
When using custom router links, (eg, react-router, next) you need to set the navigate prop on the Menu.Root component.
'use client'
import { Menu } from '@fidely-ui/react/menu'
import { useNavigate } from 'react-router-dom'
const Demo = () => {
const navigate = useNavigate()
return (
<Menu.Root navigate={({ value, node }) => navigate(`/${value}`)}>
{/* ... */}
</Menu.Root>
)
}Within Dialog
To use the Menu within a Dialog, you need to avoid portalling the Menu.Positioner to the document's body.
/* <Portal> */
<Menu.Positioner>
<Menu.Content>{/* ... */}</Menu.Content>
</Menu.Positioner>
/* </Portal> */Guide
Styling highlighted items
Use the _highlighted prop to style menu items when they are hovered or focused with keyboard navigation.
<Menu.Item _highlighted={{ bg: 'amber.5', color: 'white' }}>
Custom highlighted item
</Menu.Item>Styling open state
Use the _open prop to style the menu trigger when the menu is open or _closed when closed
<Menu.Trigger asChild>
<Button _open={{ bg: 'purple.6', color: 'white' }}>Menu</Button>
</Menu.Trigger>Props
Root
| Prop | Type | Default | Description |
|---|---|---|---|
closeOnSelect | boolean | true | Whether to close the menu when an option is selected. |
composite | boolean | true | Whether the menu is a composed with other composite widgets like a combobox or tabs. |
lazyMount | boolean | false | Whether to enable lazy mounting. |
skipAnimationOnMount | boolean | false | Whether to allow the initial presence animation. |
loopFocus | boolean | false | Whether to loop the keyboard navigation. |
typeahead | boolean | true | Whether the pressing printable characters should trigger typeahead navigation. |
unmountOnExit | boolean | false | Whether to unmount on exit. |
size | "sm" | "md" | md | The size of the component |
as | React.ElementType | - | The underlying element to render. |
asChild | boolean | - | Use the provided child element as the default rendered element, combining their props and behavior |
defaultHighlightedValue | string | - | The initial highlighted value of the menu item when rendered. Use when you don't need to control the highlighted value of the menu item. |
aria-label | string | - | The accessibility label for the menu |
anchorPoint | Point | - | The positioning point for the menu. Can be set by the context menu trigger or the button trigger |
defaultOpen | boolean | - | The initial open state of the menu when rendered. Use when you don't need to control the open state of the menu |
highlightedValue | string | - | The controlled highlighted value of the menu item |
id | string | - | The unique identifier of the machine. |
ids | Partial<{}> | - | The ids of the elements in the menu. Useful for composition |
immediate | boolean | - | Whether to synchronize the present change immediately or defer it to the next frame |
navigate | (details: NavigateDetails) => void | - | Function to navigate to the selected item if it's an anchor element |
onEscapeKeyDown | (event: KeyboardEvent) => void | - | Function called when the escape key is pressed |
onExitComplete | VoidFunction | - | Function called when the animation ends in the closed state |
onHighlightChange | (details: HighlightChangeDetails) => void | - | Function called when the highlighted menu item changes |
onInteractOutside | (event: InteractOutsideEvent) => void | - | Function called when an interaction happens outside the component |
onPointerDownOutside | (event: PointerDownOutsideEvent) => void | - | Function called when the pointer is pressed down outside the component |
onOpenChange | (details: OpenChangeDetails) => void | - | Function called when the menu opens or closes |
onRequestDismiss | (event: LayerDismissEvent) => void | - | Function called when this layer is closed due to a parent layer being closed |
onSelect | (details: SelectionDetails) => void | - | Function called when a menu item is selected |
open | boolean | - | The controlled open state of the menu |
positioning | PositioningOptions | - | The options used to dynamically position the menu |
present | boolean | - | Whether the node is present (controlled by the user) |
Item
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | The unique value of the menu item option, always Required. |
as | React.ElementType | - | The underlying element to render. |
asChild | boolean | - | Use the provided child element as the default rendered element, combining their props and behavior |
closeOnSelect | boolean | - | Whether the menu should be closed when the option is selected. |
onSelect | VoidFunction | - | The function to call when the item is selected |
disabled | boolean | - | Whether the menu item is disabled. |
valueText | string | — | The textual value of the option. Used in typeahead navigation of the menu. If not provided, the text content of the menu item will be used. |