Context Menu
Opens a menu at the exact spot you right-click, providing quick access to relevant options.
Basic
This component excels in desktop applications but has significant limitations. Its lack of accessibility and mobile compatibility are notable drawbacks. React Aria hasn't released an official version of this component yet, possibly due to these issues.
Unless it's absolutely necessary for your project, I'd advise against using it. For most scenarios requiring accessible, rich interfaces, consider alternatives like Menus or Popovers instead. These components typically offer better cross-platform support and accessibility features.
The initial release will be an alpha version due to ongoing issues with cursor positioning accuracy. If you're able to contribute, I'd appreciate your help in submitting a pull request to address this component's limitations.
Installation
If you hit any issues, make sure you check out the installation guide here for more information.
npx @intentui/cli@latest add context-menu
Composed Components
When you install this component via the CLI, it automatically loads all composed components, so you don’t need to add them individually.
The Context Menu comes packed with several components to enhance functionality and provide a seamless experience.
Manual Installation
Make sure you also install the composed components and the required packages for the component to function properly.
npm i react-aria-components @intentui/icons
You can copy the code below and paste it into your component folder.
"use client"
import { createContext, use, useRef, useState } from "react"
import { twMerge } from "tailwind-merge"
import type { MenuContentProps } from "./menu"
import { Menu } from "./menu"
interface ContextMenuTriggerContextType {
buttonRef: React.RefObject<HTMLButtonElement | null>
contextMenuOffset: { offset: number; crossOffset: number } | null
setContextMenuOffset: React.Dispatch<
React.SetStateAction<{ offset: number; crossOffset: number } | null>
>
}
const ContextMenuTriggerContext = createContext<ContextMenuTriggerContextType | undefined>(
undefined,
)
const useContextMenuTrigger = () => {
const context = use(ContextMenuTriggerContext)
if (!context) {
throw new Error("useContextMenuTrigger must be used within a ContextMenuTrigger")
}
return context
}
interface ContextMenuProps {
children: React.ReactNode
}
const ContextMenu = ({ children }: ContextMenuProps) => {
const [contextMenuOffset, setContextMenuOffset] = useState<{
offset: number
crossOffset: number
} | null>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
return (
<ContextMenuTriggerContext.Provider
value={{ buttonRef, contextMenuOffset, setContextMenuOffset }}
>
{children}
</ContextMenuTriggerContext.Provider>
)
}
type ContextMenuTriggerProps = React.ButtonHTMLAttributes<HTMLButtonElement>
const ContextMenuTrigger = ({ className, ...props }: ContextMenuTriggerProps) => {
const { buttonRef, setContextMenuOffset } = useContextMenuTrigger()
const onContextMenu = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
const rect = e.currentTarget.getBoundingClientRect()
setContextMenuOffset({
offset: e.clientY - rect.bottom,
crossOffset: e.clientX - rect.left,
})
}
return (
<button
className={twMerge(
"cursor-default focus:outline-hidden disabled:opacity-60 disabled:forced-colors:disabled:text-[GrayText]",
className,
)}
ref={buttonRef}
aria-haspopup="menu"
onContextMenu={onContextMenu}
{...props}
/>
)
}
type ContextMenuContentProps<T> = Omit<
MenuContentProps<T>,
"showArrow" | "isOpen" | "onOpenChange" | "triggerRef" | "placement" | "shouldFlip"
>
const ContextMenuContent = <T extends object>(props: ContextMenuContentProps<T>) => {
const { contextMenuOffset, setContextMenuOffset, buttonRef } = useContextMenuTrigger()
return contextMenuOffset ? (
<Menu.Content
isOpen={!!contextMenuOffset}
onOpenChange={() => setContextMenuOffset(null)}
triggerRef={buttonRef}
shouldFlip={false}
placement="bottom left"
offset={contextMenuOffset?.offset}
crossOffset={contextMenuOffset?.crossOffset}
onClose={() => setContextMenuOffset(null)}
{...props}
/>
) : null
}
const ContextMenuItem = Menu.Item
const ContextMenuSeparator = Menu.Separator
const ContextMenuItemDetails = Menu.ItemDetails
const ContextMenuSection = Menu.Section
const ContextMenuHeader = Menu.Header
const ContextMenuKeyboard = Menu.Keyboard
const ContextMenuLabel = Menu.Label
ContextMenu.Trigger = ContextMenuTrigger
ContextMenu.Content = ContextMenuContent
ContextMenu.Item = ContextMenuItem
ContextMenu.Label = ContextMenuLabel
ContextMenu.Separator = ContextMenuSeparator
ContextMenu.ItemDetails = ContextMenuItemDetails
ContextMenu.Section = ContextMenuSection
ContextMenu.Header = ContextMenuHeader
ContextMenu.Keyboard = ContextMenuKeyboard
export type { ContextMenuProps }
export { ContextMenu }
Item Details
You can add details to menu items by using the ContextMenuItemDetails
component.
Danger
Designate a menu item as dangerous.
With Icon
Enhance context menu item by adding icons.
Separator
Separate context menu items with a separator.
Context Sub Menu
Sorry, this component doesn't exist yet. It's on our roadmap.
Disabled
Disable specific menu items.
Also, you can disable items directly in MenuContent
by using the disabledKeys
prop.
<ContextMenu.Content disabledKeys={['gsu']} />