Context Menu
Displays a menu of actions triggered by a right click.
Right click here
Back⌘[
Forward⌘]
Reload⌘R
More Tools
Save Page...
Create Shortcut...
Name Window...
Developer Tools
Delete
Show Bookmarks
Show Full URLs
People
Pedro Duarte
Colm Tuite
import { css } from 'remix/component'
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,import { css } from 'remix/component'
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from '#app/ui/context-menu.tsx'
export function ContextMenuDemo() {
return () => (
<ContextMenu>
<ContextMenuTrigger
mix={css({
display: 'flex',
height: '150px',
width: '300px',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 'calc(var(--radius) - 2px)',
borderWidth: '1px',
borderStyle: 'dashed',
fontSize: '0.875rem',
lineHeight: 'calc(1.25 / 0.875)',
})}
>
Right click here
</ContextMenuTrigger>
<ContextMenuContent mix={css({ width: '13rem' })}>
<ContextMenuItem inset>
Back
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset disabled>
Forward
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset>
Reload
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent mix={css({ width: '11rem' })}>
<ContextMenuItem>Save Page...</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
<ContextMenuItem>Name Window...</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Developer Tools</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem variant="destructive">Delete</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuCheckboxItem checked>Show Bookmarks</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
<ContextMenuSeparator />
<ContextMenuRadioGroup value="pedro">
<ContextMenuLabel inset>People</ContextMenuLabel>
<ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
)
}Installation
$ bunx --bun shadcn@latest add https://www.shadcn-remix.com/r/context-menu.json$ npx shadcn@latest add https://www.shadcn-remix.com/r/context-menu.json$ pnpm dlx shadcn@latest add https://www.shadcn-remix.com/r/context-menu.json$ yarn dlx shadcn@latest add https://www.shadcn-remix.com/r/context-menu.jsonInstall the following dependencies:
$ bun add remix @remix-run/ui$ npm install remix @remix-run/ui$ pnpm add remix @remix-run/ui$ yarn add remix @remix-run/uiCopy and paste the following code into your project.
import { css, on, type Handle, type Props, type RemixNode } from 'remix/component'
import type { AnchorOptions } from '@remix-run/ui/anchor'
import * as menu from '@remix-run/ui/menu'
import * as popover from '@remix-run/ui/popover'
import { CheckIcon, ChevronRightIcon, CircleIcon } from './icons.tsx'
/** The parts of `@remix-run/ui/menu`'s context that the content components drive. */
interface MenuState {
readonly isOpen: boolean
open(options?: { strategy?: 'first' | 'last' | 'list' | 'none' }): Promise<void>
closeAll(): Promise<void>
}
export interface ContextMenuProps {
/** Called when the menu opens or closes. */
onOpenChange?: (open: boolean) => void
children?: RemixNode
}
export interface ContextMenuSubProps extends ContextMenuProps {
/** Controlled open state. */
open?: boolean
/** Open on first render when uncontrolled. */
defaultOpen?: boolean
}
interface OpenStateOwner {
readonly props: ContextMenuSubProps
}
export function ContextMenu(handle: Handle<ContextMenuProps, OpenStateOwner>) {
handle.context.set({
get props() {
return handle.props
},
})
return () => <menu.Context>{handle.props.children}</menu.Context>
}
/** Radix portals menu content; here content renders in the top layer (`popover`) instead. */
export function ContextMenuPortal(handle: Handle<{ children?: RemixNode }>) {
return () => handle.props.children
}
export interface ContextMenuTriggerProps extends Props<'span'> {
disabled?: boolean
}
/**
* The area that opens the menu on right click (or the ContextMenu key / Shift+F10 while focused).
* The menu opens at the pointer position.
*/
export function ContextMenuTrigger(handle: Handle<ContextMenuTriggerProps>) {
let contextMenu = handle.context.get(menu.Context)
return () => {
let { disabled = false, mix, ...props } = handle.props
return (
<span
data-slot="context-menu-trigger"
data-state={contextMenu.isOpen ? 'open' : 'closed'}
data-disabled={disabled ? '' : undefined}
{...props}
mix={[disabled ? null : menu.contextTrigger(), mix]}
/>
)
}
}
/**
* Reports open changes of a menu to `onOpenChange` and applies `open`/`defaultOpen` prop changes.
* Call once per render of the menu's content.
*/
function createOpenStateSync(handle: Handle<object>, contextMenu: MenuState) {
let reportedOpen = false
let appliedOpen: boolean | undefined
let initialized = false
return (props: ContextMenuSubProps) => {
let isOpen = contextMenu.isOpen
if (isOpen !== reportedOpen) {
reportedOpen = isOpen
handle.queueTask(() => props.onOpenChange?.(isOpen))
}
let requested = initialized ? props.open : (props.open ?? props.defaultOpen)
initialized = true
if (requested === undefined || requested === appliedOpen) return
appliedOpen = requested
if (requested === isOpen) return
handle.queueTask(() => {
if (requested) void contextMenu.open({ strategy: 'list' })
else void contextMenu.closeAll()
})
}
}
type Side = 'top' | 'right' | 'bottom' | 'left'
type Align = 'start' | 'center' | 'end'
/**
* Overrides the anchor options registered by the trigger with the content's Radix-style
* `side`/`align`/offsets right before the popover is positioned, and exposes the trigger size as
* `--radix-context-menu-trigger-width|height`.
*/
function positionContent(
surface: popover.PopoverContext,
node: HTMLElement,
side: Side,
align: Align,
sideOffset: number,
alignOffset: number,
extra: AnchorOptions = {},
) {
let anchor = surface.anchor
if (!anchor) return
let alignShift = align === 'end' ? -alignOffset : alignOffset
let vertical = side === 'top' || side === 'bottom'
surface.anchor = {
target: anchor.target,
options: {
...anchor.options,
placement: align === 'center' ? side : `${side}-${align}`,
offset: sideOffset,
offsetX: vertical ? alignShift : 0,
offsetY: vertical ? 0 : alignShift,
...extra,
},
}
if (anchor.target instanceof HTMLElement) {
node.style.setProperty('--radix-context-menu-trigger-width', `${anchor.target.offsetWidth}px`)
node.style.setProperty('--radix-context-menu-trigger-height', `${anchor.target.offsetHeight}px`)
}
}
const surfaceAnimation = {
'&[data-state="open"]': {
animation: 'enter 150ms',
'--tw-enter-opacity': 0,
'--tw-enter-scale': 0.95,
},
// The popover leaves the top layer as soon as it is hidden; keep it rendered (display/overlay
// allow-discrete) for the exit animation unless the menu closes without animation.
'&[data-state="closed"]:not([data-close-animation="none"])': {
animation: 'exit 150ms',
animationFillMode: 'forwards',
'--tw-exit-opacity': 0,
'--tw-exit-scale': 0.95,
transition: 'display 150ms allow-discrete, overlay 150ms allow-discrete',
},
'&[data-anchor-placement^="bottom"]': { '--tw-enter-translate-y': '-0.5rem', transformOrigin: 'top' },
'&[data-anchor-placement^="top"]': { '--tw-enter-translate-y': '0.5rem', transformOrigin: 'bottom' },
'&[data-anchor-placement^="right"]': { '--tw-enter-translate-x': '-0.5rem', transformOrigin: 'left' },
'&[data-anchor-placement^="left"]': { '--tw-enter-translate-x': '0.5rem', transformOrigin: 'right' },
'&[data-anchor-placement="bottom-start"]': { transformOrigin: 'top left' },
'&[data-anchor-placement="bottom-end"]': { transformOrigin: 'top right' },
'&[data-anchor-placement="top-start"]': { transformOrigin: 'bottom left' },
'&[data-anchor-placement="top-end"]': { transformOrigin: 'bottom right' },
'&[data-anchor-placement="right-start"]': { transformOrigin: 'top left' },
'&[data-anchor-placement="right-end"]': { transformOrigin: 'bottom left' },
'&[data-anchor-placement="left-start"]': { transformOrigin: 'top right' },
'&[data-anchor-placement="left-end"]': { transformOrigin: 'bottom right' },
} as const
const contentStyles = css({
minWidth: '8rem',
overflowX: 'hidden',
overflowY: 'auto',
borderRadius: 'calc(var(--radius) - 2px)',
borderWidth: '1px',
backgroundColor: 'var(--popover)',
padding: '0.25rem',
color: 'var(--popover-foreground)',
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
...surfaceAnimation,
})
const listStyles = css({ outlineStyle: 'none' })
export type ContextMenuContentProps = Props<'div'>
export function ContextMenuContent(handle: Handle<ContextMenuContentProps>) {
let contextMenu = handle.context.get(menu.Context)
let surface = handle.context.get(popover.Context)
let owner = handle.context.get(ContextMenu)
let syncOpenState = createOpenStateSync(handle, contextMenu)
return () => {
let { mix, children, ...props } = handle.props
syncOpenState(owner.props)
return (
<div
data-slot="context-menu-content"
data-state={contextMenu.isOpen ? 'open' : 'closed'}
{...props}
mix={[
contentStyles,
// Radix places context content at the pointer with `side="right" sideOffset={2} align="start"`.
on('beforetoggle', (event) => {
if (event.newState === 'open') positionContent(surface, event.currentTarget, 'right', 'start', 2, 0)
}),
menu.popover(),
mix,
]}
>
<div mix={[listStyles, menu.list()]}>{children}</div>
</div>
)
}
}
export function ContextMenuGroup(handle: Handle<Props<'div'>>) {
return () => <div role="group" data-slot="context-menu-group" {...handle.props} />
}
const itemStyles = css({
position: 'relative',
display: 'flex',
cursor: 'default',
alignItems: 'center',
gap: '0.5rem',
borderRadius: 'calc(var(--radius) - 4px)',
padding: '0.375rem 0.5rem',
fontSize: '0.875rem',
lineHeight: 'calc(1.25 / 0.875)',
outlineStyle: 'none',
userSelect: 'none',
'&[data-highlighted]': { backgroundColor: 'var(--accent)', color: 'var(--accent-foreground)' },
'&[data-disabled]': { pointerEvents: 'none', opacity: 0.5 },
'&[data-inset]': { paddingLeft: '2rem' },
'&[data-variant="destructive"]': { color: 'var(--destructive)' },
'&[data-variant="destructive"][data-highlighted]': {
backgroundColor: 'color-mix(in oklab, var(--destructive) 10%, transparent)',
color: 'var(--destructive)',
},
'.dark &[data-variant="destructive"][data-highlighted]': {
backgroundColor: 'color-mix(in oklab, var(--destructive) 20%, transparent)',
},
'& svg': { pointerEvents: 'none', flexShrink: 0 },
'& svg:not([class*="size-"])': { width: '1rem', height: '1rem' },
'& svg:not([class*="text-"])': { color: 'var(--muted-foreground)' },
'&[data-variant="destructive"] > svg': { color: 'var(--destructive) !important' },
})
export interface ContextMenuItemProps extends Props<'div'> {
inset?: boolean
variant?: 'default' | 'destructive'
disabled?: boolean
/** Text used for typeahead; defaults to the item's text content. */
textValue?: string
/** Called when the item is selected (click, Enter or Space). The menu then closes. */
onSelect?: (event: menu.MenuSelectEvent) => void
}
export function ContextMenuItem(handle: Handle<ContextMenuItemProps>) {
return () => {
let { inset, variant = 'default', disabled = false, textValue, onSelect, mix, ...props } = handle.props
return (
<div
data-slot="context-menu-item"
data-inset={inset ? 'true' : undefined}
data-variant={variant}
data-disabled={disabled ? '' : undefined}
{...props}
mix={[
itemStyles,
menu.item({ name: handle.id, disabled, label: textValue }),
menu.onMenuSelect((event) => {
if (event.target === event.currentTarget) onSelect?.(event)
}),
mix,
]}
/>
)
}
}
const choiceItemStyles = css({
position: 'relative',
display: 'flex',
cursor: 'default',
alignItems: 'center',
gap: '0.5rem',
borderRadius: 'calc(var(--radius) - 4px)',
padding: '0.375rem 0.5rem 0.375rem 2rem',
fontSize: '0.875rem',
lineHeight: 'calc(1.25 / 0.875)',
outlineStyle: 'none',
userSelect: 'none',
'&[data-highlighted]': { backgroundColor: 'var(--accent)', color: 'var(--accent-foreground)' },
'&[data-disabled]': { pointerEvents: 'none', opacity: 0.5 },
'& svg': { pointerEvents: 'none', flexShrink: 0 },
'& svg:not([class*="size-"])': { width: '1rem', height: '1rem' },
})
const indicatorStyles = css({
pointerEvents: 'none',
position: 'absolute',
left: '0.5rem',
display: 'flex',
width: '0.875rem',
height: '0.875rem',
alignItems: 'center',
justifyContent: 'center',
})
const checkIconStyles = css({ width: '1rem', height: '1rem' })
const circleIconStyles = css({ width: '0.5rem', height: '0.5rem', fill: 'currentColor' })
export interface ContextMenuCheckboxItemProps extends Props<'div'> {
checked?: boolean | 'indeterminate'
onCheckedChange?: (checked: boolean) => void
disabled?: boolean
textValue?: string
onSelect?: (event: menu.MenuSelectEvent) => void
}
export function ContextMenuCheckboxItem(handle: Handle<ContextMenuCheckboxItemProps>) {
return () => {
let { checked = false, onCheckedChange, disabled = false, textValue, onSelect, mix, children, ...props } =
handle.props
return (
<div
data-slot="context-menu-checkbox-item"
data-state={checked === 'indeterminate' ? 'indeterminate' : checked ? 'checked' : 'unchecked'}
data-disabled={disabled ? '' : undefined}
{...props}
mix={[
choiceItemStyles,
menu.item({ name: handle.id, type: 'checkbox', checked: checked === true, disabled, label: textValue }),
menu.onMenuSelect((event) => {
if (event.target !== event.currentTarget) return
onSelect?.(event)
onCheckedChange?.(event.item.checked === true)
}),
mix,
]}
>
<span mix={indicatorStyles}>{checked !== false && <CheckIcon class="size-4" mix={checkIconStyles} />}</span>
{children}
</div>
)
}
}
export interface ContextMenuRadioGroupProps extends Props<'div'> {
value?: string
onValueChange?: (value: string) => void
}
interface RadioGroupContext {
readonly name: string
readonly value: string | undefined
select(value: string): void
}
export function ContextMenuRadioGroup(handle: Handle<ContextMenuRadioGroupProps, RadioGroupContext>) {
handle.context.set({
name: handle.id,
get value() {
return handle.props.value
},
select(value) {
handle.props.onValueChange?.(value)
},
})
return () => {
let { value, onValueChange, ...props } = handle.props
return <div role="group" data-slot="context-menu-radio-group" {...props} />
}
}
export interface ContextMenuRadioItemProps extends Props<'div'> {
value: string
disabled?: boolean
textValue?: string
onSelect?: (event: menu.MenuSelectEvent) => void
}
export function ContextMenuRadioItem(handle: Handle<ContextMenuRadioItemProps>) {
let group = handle.context.get(ContextMenuRadioGroup)
return () => {
let { value, disabled = false, textValue, onSelect, mix, children, ...props } = handle.props
let checked = group.value === value
return (
<div
data-slot="context-menu-radio-item"
data-state={checked ? 'checked' : 'unchecked'}
data-disabled={disabled ? '' : undefined}
{...props}
mix={[
choiceItemStyles,
menu.item({ name: group.name, type: 'radio', value, checked, disabled, label: textValue }),
menu.onMenuSelect((event) => {
if (event.target !== event.currentTarget) return
onSelect?.(event)
group.select(value)
}),
mix,
]}
>
<span mix={indicatorStyles}>{checked && <CircleIcon class="size-2 fill-current" mix={circleIconStyles} />}</span>
{children}
</div>
)
}
}
const labelStyles = css({
padding: '0.375rem 0.5rem',
fontSize: '0.875rem',
lineHeight: 'calc(1.25 / 0.875)',
fontWeight: 500,
'&[data-inset]': { paddingLeft: '2rem' },
})
export interface ContextMenuLabelProps extends Props<'div'> {
inset?: boolean
}
export function ContextMenuLabel(handle: Handle<ContextMenuLabelProps>) {
return () => {
let { inset, mix, ...props } = handle.props
return (
<div
data-slot="context-menu-label"
data-inset={inset ? 'true' : undefined}
{...props}
mix={[labelStyles, mix]}
/>
)
}
}
const separatorStyles = css({
marginInline: '-0.25rem',
marginBlock: '0.25rem',
height: '1px',
backgroundColor: 'var(--border)',
})
export function ContextMenuSeparator(handle: Handle<Props<'div'>>) {
return () => {
let { mix, ...props } = handle.props
return (
<div
role="separator"
aria-orientation="horizontal"
data-slot="context-menu-separator"
{...props}
mix={[separatorStyles, mix]}
/>
)
}
}
const shortcutStyles = css({
marginLeft: 'auto',
fontSize: '0.75rem',
lineHeight: 'calc(1 / 0.75)',
letterSpacing: '0.1em',
color: 'var(--muted-foreground)',
})
export function ContextMenuShortcut(handle: Handle<Props<'span'>>) {
return () => {
let { mix, ...props } = handle.props
return <span data-slot="context-menu-shortcut" {...props} mix={[shortcutStyles, mix]} />
}
}
export function ContextMenuSub(handle: Handle<ContextMenuSubProps, OpenStateOwner>) {
handle.context.set({
get props() {
return handle.props
},
})
return () => <menu.Context>{handle.props.children}</menu.Context>
}
const subTriggerStyles = css({
display: 'flex',
cursor: 'default',
alignItems: 'center',
gap: '0.5rem',
borderRadius: 'calc(var(--radius) - 4px)',
padding: '0.375rem 0.5rem',
fontSize: '0.875rem',
lineHeight: 'calc(1.25 / 0.875)',
outlineStyle: 'none',
userSelect: 'none',
'&[data-highlighted], &[data-state="open"]': {
backgroundColor: 'var(--accent)',
color: 'var(--accent-foreground)',
},
'&[data-inset]': { paddingLeft: '2rem' },
'& svg': { pointerEvents: 'none', flexShrink: 0 },
'& svg:not([class*="size-"])': { width: '1rem', height: '1rem' },
'& svg:not([class*="text-"])': { color: 'var(--muted-foreground)' },
})
const chevronStyles = css({ marginLeft: 'auto', width: '1rem', height: '1rem' })
export interface ContextMenuSubTriggerProps extends Props<'div'> {
inset?: boolean
disabled?: boolean
textValue?: string
}
export function ContextMenuSubTrigger(handle: Handle<ContextMenuSubTriggerProps>) {
let sub = handle.context.get(menu.Context)
return () => {
let { inset, disabled = false, textValue, mix, children, ...props } = handle.props
return (
<div
data-slot="context-menu-sub-trigger"
data-inset={inset ? 'true' : undefined}
data-state={sub.isOpen ? 'open' : 'closed'}
data-disabled={disabled ? '' : undefined}
{...props}
mix={[subTriggerStyles, menu.submenuTrigger({ disabled, label: textValue }), mix]}
>
{children}
<ChevronRightIcon class="size-4" mix={chevronStyles} />
</div>
)
}
}
const subContentStyles = css({
minWidth: '8rem',
overflow: 'hidden',
borderRadius: 'calc(var(--radius) - 2px)',
borderWidth: '1px',
backgroundColor: 'var(--popover)',
padding: '0.25rem',
color: 'var(--popover-foreground)',
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
...surfaceAnimation,
})
export interface ContextMenuSubContentProps extends Props<'div'> {
sideOffset?: number
alignOffset?: number
}
export function ContextMenuSubContent(handle: Handle<ContextMenuSubContentProps>) {
let sub = handle.context.get(menu.Context)
let surface = handle.context.get(popover.Context)
let owner = handle.context.get(ContextMenuSub)
let syncOpenState = createOpenStateSync(handle, sub)
return () => {
let { sideOffset = 0, alignOffset = 0, mix, children, ...props } = handle.props
syncOpenState(owner.props)
return (
<div
data-slot="context-menu-sub-content"
data-state={sub.isOpen ? 'open' : 'closed'}
{...props}
mix={[
subContentStyles,
on('beforetoggle', (event) => {
if (event.newState !== 'open') return
positionContent(surface, event.currentTarget, 'right', 'start', sideOffset, alignOffset, {
relativeTo: undefined,
})
}),
menu.popover(),
mix,
]}
>
<div mix={[listStyles, menu.list()]}>{children}</div>
</div>
)
}
}Update the import paths to match your project setup.
Usage
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from '#app/ui/context-menu.tsx'<ContextMenu>
<ContextMenuTrigger>Right click here</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Profile</ContextMenuItem>
<ContextMenuItem>Billing</ContextMenuItem>
<ContextMenuItem>Team</ContextMenuItem>
<ContextMenuItem>Subscription</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>API Reference
ContextMenu
| Prop | Type | Default |
|---|---|---|
onOpenChange | ((open: boolean) => void) | - |
ContextMenuPortal
Radix portals menu content; here content renders in the top layer (popover) instead.
ContextMenuTrigger
The area that opens the menu on right click (or the ContextMenu key / Shift+F10 while focused). The menu opens at the pointer position.
| Prop | Type | Default |
|---|---|---|
disabled | boolean | false |
mix | MixValue | - |
ContextMenuContent
| Prop | Type | Default |
|---|---|---|
mix | MixValue | - |
ContextMenuGroup
| Prop | Type | Default |
|---|---|---|
mix | MixValue | - |
ContextMenuItem
| Prop | Type | Default |
|---|---|---|
inset | boolean | - |
variant | "default" | "destructive" | "default" |
disabled | boolean | false |
textValue | string | - |
onSelect | ((event: MenuSelectEvent) => void) | - |
mix | MixValue | - |
ContextMenuCheckboxItem
| Prop | Type | Default |
|---|---|---|
checked | boolean | "indeterminate" | false |
onCheckedChange | ((checked: boolean) => void) | - |
disabled | boolean | false |
textValue | string | - |
onSelect | ((event: MenuSelectEvent) => void) | - |
mix | MixValue | - |
ContextMenuRadioGroup
| Prop | Type | Default |
|---|---|---|
value | string | - |
onValueChange | ((value: string) => void) | - |
mix | MixValue | - |
ContextMenuRadioItem
| Prop | Type | Default |
|---|---|---|
value | string | - |
disabled | boolean | false |
textValue | string | - |
onSelect | ((event: MenuSelectEvent) => void) | - |
mix | MixValue | - |
ContextMenuLabel
| Prop | Type | Default |
|---|---|---|
inset | boolean | - |
mix | MixValue | - |
ContextMenuSeparator
| Prop | Type | Default |
|---|---|---|
mix | MixValue | - |
ContextMenuShortcut
| Prop | Type | Default |
|---|---|---|
mix | MixValue | - |
ContextMenuSub
| Prop | Type | Default |
|---|---|---|
open | boolean | - |
defaultOpen | boolean | - |
onOpenChange | ((open: boolean) => void) | - |
ContextMenuSubTrigger
| Prop | Type | Default |
|---|---|---|
inset | boolean | - |
disabled | boolean | false |
textValue | string | - |
mix | MixValue | - |
ContextMenuSubContent
| Prop | Type | Default |
|---|---|---|
sideOffset | number | 0 |
alignOffset | number | 0 |
mix | MixValue | - |
On This Page
InstallationUsageAPI ReferenceContextMenuContextMenuPortalContextMenuTriggerContextMenuContentContextMenuGroupContextMenuItemContextMenuCheckboxItemContextMenuRadioGroupContextMenuRadioItemContextMenuLabelContextMenuSeparatorContextMenuShortcutContextMenuSubContextMenuSubTriggerContextMenuSubContent