Installation
npx @park-ui/cli add menuAdd Component
Copy the code snippet below into you components folder.
'use client'
import { Menu, useMenuItemContext } from '@ark-ui/react/menu'
import { CheckIcon, ChevronDownIcon } from 'lucide-react'
import { type ComponentProps, forwardRef } from 'react'
import { createStyleContext, type HTMLStyledProps } from 'styled-system/jsx'
import { menu } from 'styled-system/recipes'
const { withRootProvider, withContext } = createStyleContext(menu)
export type RootProps = ComponentProps<typeof Root>
export const Root = withRootProvider(Menu.Root, {
defaultProps: { unmountOnExit: true, lazyMount: true },
})
export const RootProvider = withRootProvider(Menu.Root, {
defaultProps: { unmountOnExit: true, lazyMount: true },
})
export const Arrow = withContext(Menu.Arrow, 'arrow')
export const ArrowTip = withContext(Menu.ArrowTip, 'arrowTip')
export const CheckboxItem = withContext(Menu.CheckboxItem, 'item')
export const Content = withContext(Menu.Content, 'content')
export const ContextTrigger = withContext(Menu.ContextTrigger, 'contextTrigger')
export const Indicator = withContext(Menu.Indicator, 'indicator', {
defaultProps: { children: <ChevronDownIcon /> },
})
export const Item = withContext(Menu.Item, 'item')
export const ItemGroup = withContext(Menu.ItemGroup, 'itemGroup')
export const ItemGroupLabel = withContext(Menu.ItemGroupLabel, 'itemGroupLabel')
export const ItemText = withContext(Menu.ItemText, 'itemText')
export const Positioner = withContext(Menu.Positioner, 'positioner')
export const RadioItem = withContext(Menu.RadioItem, 'item')
export const RadioItemGroup = withContext(Menu.RadioItemGroup, 'itemGroup')
export const Separator = withContext(Menu.Separator, 'separator')
export const Trigger = withContext(Menu.Trigger, 'trigger')
export const TriggerItem = withContext(Menu.TriggerItem, 'item')
export {
MenuContext as Context,
type MenuSelectionDetails as SelectionDetails,
} from '@ark-ui/react/menu'
const StyledItemIndicator = withContext(Menu.ItemIndicator, 'itemIndicator')
export const ItemIndicator = forwardRef<HTMLDivElement, HTMLStyledProps<'div'>>(
function ItemIndicator(props, ref) {
const item = useMenuItemContext()
return item.checked ? (
<StyledItemIndicator ref={ref} {...props}>
<CheckIcon />
</StyledItemIndicator>
) : (
<svg aria-hidden="true" focusable="false" />
)
},
)
Integrate Recipe
Integrate this recipe in to your Panda config.
import { menuAnatomy } from '@ark-ui/react/menu'
import { defineSlotRecipe } from '@pandacss/dev'
export const menu = defineSlotRecipe({
className: 'menu',
slots: menuAnatomy.keys(),
base: {
content: {
'--menu-z-index': 'zIndex.dropdown',
bg: 'gray.surface.bg',
borderRadius: 'l3',
boxShadow: 'md',
display: 'flex',
flexDirection: 'column',
maxH: 'min(var(--available-height), {sizes.96})',
minWidth: 'max(var(--reference-width), {sizes.40})',
outline: '0',
overflow: 'hidden',
overflowY: 'auto',
position: 'relative',
zIndex: 'calc(var(--menu-z-index) + var(--layer-index, 0))',
_open: {
animationStyle: 'slide-fade-in',
animationDuration: 'fast',
},
_closed: {
animationStyle: 'slide-fade-out',
animationDuration: 'faster',
},
},
item: {
alignItems: 'center',
borderRadius: 'l2',
display: 'flex',
flex: '0 0 auto',
outline: '0',
textAlign: 'start',
textDecoration: 'none',
userSelect: 'none',
width: '100%',
_highlighted: {
bg: 'gray.surface.bg.hover',
},
_disabled: {
layerStyle: 'disabled',
},
},
trigger: {
_focusVisible: {
focusVisibleRing: 'outside',
},
},
itemGroupLabel: {
alignItems: 'flex-start',
color: 'fg.subtle',
display: 'flex',
flexDirection: 'column',
fontWeight: 'medium',
gap: '1px',
justifyContent: 'center',
_after: {
content: '""',
width: '100%',
height: '1px',
bg: 'border',
},
},
itemIndicator: {
justifyContent: 'flex-end',
display: 'flex',
flex: '1',
_checked: {
_icon: {
color: 'colorPalette.plain.fg',
},
},
},
},
defaultVariants: {
size: 'md',
},
variants: {
size: {
xs: {
content: { p: '1', gap: '0.5', textStyle: 'sm' },
item: { px: '1', minH: '8', gap: '2', _icon: { boxSize: '3.5' } },
itemGroup: { gap: '0.5' },
itemGroupLabel: { px: '1', height: '8' },
separator: { mx: '-1', my: '0.5' },
},
sm: {
content: { p: '1', gap: '0.5', textStyle: 'sm' },
item: { px: '1.5', minH: '9', gap: '2', _icon: { boxSize: '4' } },
itemGroup: { gap: '0.5' },
itemGroupLabel: { px: '1.5', height: '9' },
separator: { mx: '-1.5', my: '0.5' },
},
md: {
content: { p: '1', gap: '0.5', textStyle: 'md' },
item: { px: '2', minH: '10', gap: '2', _icon: { boxSize: '4' } },
itemGroup: { gap: '0.5' },
itemGroupLabel: { px: '2', height: '10' },
separator: { mx: '-2', my: '0.5' },
},
lg: {
content: { p: '1', gap: '0.5', textStyle: 'md' },
item: { px: '2.5', minH: '11', gap: '2', _icon: { boxSize: '4.5' } },
itemGroup: { gap: '0.5' },
itemGroupLabel: { px: '2.5', height: '11' },
separator: { mx: '-2.5', my: '0.5' },
},
xl: {
content: { p: '1', gap: '1', textStyle: 'lg' },
item: { px: '3', minH: '12', gap: '3', _icon: { boxSize: '5' } },
itemGroup: { gap: '1' },
itemGroupLabel: { px: '3', height: '12' },
separator: { mx: '-3', my: '0' },
},
},
},
})
Usage
import { Menu } from '@/components/ui'
<Menu.Root>
<Menu.Trigger />
<Menu.Positioner>
<Menu.Content>
<Menu.Arrow />
<Menu.Item />
<Menu.ItemGroup>
<Menu.Item />
</Menu.ItemGroup>
<Menu.Separator />
<Menu.CheckboxItem>
<Menu.ItemIndicator />
</Menu.CheckboxItem>
<Menu.RadioItemGroup>
<Menu.RadioItem>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
Examples
Sizes
Use the size prop to adjust the size of the menu items.
Context Menu
Use the Menu.ContextTrigger component to create a context menu.
Group
Use the Menu.ItemGroup component to group related menu items.
Submenu
Here's an example of how to create a submenu.
Radio Group
Here's an example of how to create a menu with radio items.
Checkbox Items
Here's an example of how to create a menu with checkbox items.
Avatar
Here's an example that composes the Menu with the Avatar component to display a menu underneath an avatar.
Props
Root
| Prop | Default | Type |
|---|---|---|
size | 'md' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' |
closeOnSelect | true | booleanWhether to close the menu when an option is selected |
composite | true | booleanWhether the menu is a composed with other composite widgets like a combobox or tabs |
lazyMount | false | booleanWhether to enable lazy mounting |
loopFocus | false | booleanWhether to loop the keyboard navigation. |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
typeahead | true | booleanWhether the pressing printable characters should trigger typeahead navigation |
unmountOnExit | false | booleanWhether to unmount on exit. |
anchorPoint | PointThe positioning point for the menu. Can be set by the context menu trigger or the button trigger. | |
aria-label | stringThe accessibility label for the menu | |
defaultHighlightedValue | stringThe initial highlighted value of the menu item when rendered. Use when you don't need to control the highlighted value of the menu item. | |
defaultOpen | booleanThe initial open state of the menu when rendered. Use when you don't need to control the open state of the menu. | |
highlightedValue | stringThe controlled highlighted value of the menu item. | |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
trigger: string
contextTrigger: string
content: string
groupLabel: (id: string) => string
group: (id: string) => string
positioner: string
arrow: string
}>The ids of the elements in the menu. Useful for composition. | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
navigate | (details: NavigateDetails) => voidFunction to navigate to the selected item if it's an anchor element | |
onEscapeKeyDown | (event: KeyboardEvent) => voidFunction called when the escape key is pressed | |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
onFocusOutside | (event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component | |
onHighlightChange | (details: HighlightChangeDetails) => voidFunction called when the highlighted menu item changes. | |
onInteractOutside | (event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component | |
onOpenChange | (details: OpenChangeDetails) => voidFunction called when the menu opens or closes | |
onPointerDownOutside | (event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component | |
onRequestDismiss | (event: LayerDismissEvent) => voidFunction called when this layer is closed due to a parent layer being closed | |
onSelect | (details: SelectionDetails) => voidFunction called when a menu item is selected. | |
open | booleanThe controlled open state of the menu | |
positioning | PositioningOptionsThe options used to dynamically position the menu | |
present | booleanWhether the node is present (controlled by the user) |
Item
| Prop | Default | Type |
|---|---|---|
value* | stringThe unique value of the menu item option. | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. | |
closeOnSelect | booleanWhether the menu should be closed when the option is selected. | |
disabled | booleanWhether the menu item is disabled | |
onSelect | VoidFunctionThe function to call when the item is selected | |
valueText | stringThe 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. |
RadioItemGroup
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. | |
onValueChange | (e: ValueChangeDetails) => void | |
value | string |
RadioItem
| Prop | Default | Type |
|---|---|---|
value* | stringThe value of the option | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. | |
closeOnSelect | booleanWhether the menu should be closed when the option is selected. | |
disabled | booleanWhether the menu item is disabled | |
valueText | stringThe 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. |