Usage
Use anything you like in the default slot of the ContextMenu, and right-click on it to display the menu.
<script setup lang="ts">
const items = ref([
[
{
label: 'Appearance',
children: [
{
label: 'System',
icon: 'i-lucide:monitor'
},
{
label: 'Light',
icon: 'i-lucide:sun'
},
{
label: 'Dark',
icon: 'i-lucide:moon'
}
]
}
],
[
{
label: 'Show Sidebar',
kbds: ['meta', 's']
},
{
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'd']
},
{
label: 'Collapse Pinned Tabs',
disabled: true
}
],
[
{
label: 'Refresh the Page'
},
{
label: 'Clear Cookies and Refresh'
},
{
label: 'Clear Cache and Refresh'
},
{
type: 'separator'
},
{
label: 'Developer',
children: [
[
{
label: 'View Source',
kbds: ['meta', 'shift', 'u']
},
{
label: 'Developer Tools',
kbds: ['option', 'meta', 'i']
},
{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'c']
}
],
[
{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'j']
}
]
]
}
]
])
</script>
<template>
<PContextMenu :items="items">
<div
class="flex items-center justify-center rounded-md border border-dashed border-border-accented text-sm aspect-video w-72"
>
Right click here
</div>
</PContextMenu>
</template>
Items
Use the items prop as an array of objects with the following properties:
label?: stringicon?: stringavatar?: AvatarPropskbds?: string[] | KbdProps[]type?: "link" | "label" | "separator" | "checkbox"color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"checked?: booleandisabled?: booleanslot?: stringonSelect?: (e: Event) => voidonUpdateChecked?: (checked: boolean) => voidchildren?: PContextMenuItem[] | PContextMenuItem[][]class?: anypohon?: { item?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelExternalIcon?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue }
You can pass any property from the Link component such as to, target, etc.
<script setup lang="ts">
import type { PContextMenuItem } from 'pohon-ui'
const items = ref<PContextMenuItem[][]>([
[
{
label: 'Appearance',
children: [
{
label: 'System',
icon: 'i-lucide:monitor'
},
{
label: 'Light',
icon: 'i-lucide:sun'
},
{
label: 'Dark',
icon: 'i-lucide:moon'
}
]
}
],
[
{
label: 'Show Sidebar',
kbds: ['meta', 's']
},
{
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'd']
},
{
label: 'Collapse Pinned Tabs',
disabled: true
}
],
[
{
label: 'Refresh the Page'
},
{
label: 'Clear Cookies and Refresh'
},
{
label: 'Clear Cache and Refresh'
},
{
type: 'separator'
},
{
label: 'Developer',
children: [
[
{
label: 'View Source',
kbds: ['meta', 'shift', 'u']
},
{
label: 'Developer Tools',
kbds: ['option', 'meta', 'i']
},
{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'c']
}
],
[
{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'j']
}
]
]
}
]
])
</script>
<template>
<PContextMenu
:items="items"
:pohon="{
content: 'w-48'
}"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-border-accented text-sm aspect-video w-72"
>
Right click here
</div>
</PContextMenu>
</template>
items prop to create separated groups of items.children array of objects with the same properties as the items prop to create a nested menu which can be controlled using the open, defaultOpen and content properties.Size
Use the size prop to change the size of the ContextMenu.
<script setup lang="ts">
import type { PContextMenuItem } from 'pohon-ui'
const items = ref<PContextMenuItem[]>([
{
label: 'System',
icon: 'i-lucide:monitor'
},
{
label: 'Light',
icon: 'i-lucide:sun'
},
{
label: 'Dark',
icon: 'i-lucide:moon'
}
])
</script>
<template>
<PContextMenu
size="xl"
:items="items"
:pohon="{
content: 'w-48'
}"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-border-accented text-sm aspect-video w-72"
>
Right click here
</div>
</PContextMenu>
</template>
Modal
Use the modal prop to control whether the ContextMenu blocks interaction with outside content. Defaults to true.
<script setup lang="ts">
import type { PContextMenuItem } from 'pohon-ui'
const items = ref<PContextMenuItem[]>([
{
label: 'System',
icon: 'i-lucide:monitor'
},
{
label: 'Light',
icon: 'i-lucide:sun'
},
{
label: 'Dark',
icon: 'i-lucide:moon'
}
])
</script>
<template>
<PContextMenu
:modal="false"
:items="items"
:pohon="{
content: 'w-48'
}"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"
>
Right click here
</div>
</PContextMenu>
</template>
Disabled
Use the disabled prop to disable the ContextMenu.
<script setup lang="ts">
import type { PContextMenuItem } from 'pohon-ui'
const items = ref<PContextMenuItem[]>([
{
label: 'System',
icon: 'i-lucide:monitor'
},
{
label: 'Light',
icon: 'i-lucide:sun'
},
{
label: 'Dark',
icon: 'i-lucide:moon'
}
])
</script>
<template>
<PContextMenu
disabled
:items="items"
:pohon="{
content: 'w-48'
}"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-border-accented text-sm aspect-video w-72"
>
Right click here
</div>
</PContextMenu>
</template>
Examples
With checkbox items
You can use the type property with checkbox and use the checked / onUpdateChecked properties to control the checked state of the item.
<script setup lang="ts">
import type { PContextMenuItem } from 'pohon-ui';
import { computed, ref } from 'vue';
const showSidebar = ref(true);
const showToolbar = ref(false);
const items = computed<Array<PContextMenuItem>>(() => [{
label: 'View',
type: 'label' as const,
}, {
type: 'separator' as const,
}, {
label: 'Show Sidebar',
type: 'checkbox' as const,
checked: showSidebar.value,
onUpdateChecked(checked: boolean) {
showSidebar.value = checked;
},
onSelect(e: Event) {
e.preventDefault();
},
}, {
label: 'Show Toolbar',
type: 'checkbox' as const,
checked: showToolbar.value,
onUpdateChecked(checked: boolean) {
showToolbar.value = checked;
},
}, {
label: 'Collapse Pinned Tabs',
type: 'checkbox' as const,
disabled: true,
}]);
</script>
<template>
<PContextMenu
:items="items"
:pohon="{ content: 'w-48' }"
>
<div class="text-sm border border-border-accented rounded-md border-dashed flex w-72 aspect-video items-center justify-center">
Right click here
</div>
</PContextMenu>
</template>
checked state of items, it's recommended to wrap your items array inside a computed.With color items
You can use the color property to highlight certain items with a color.
<script setup lang="ts">
import type { PContextMenuItem } from 'pohon-ui';
const items: Array<Array<PContextMenuItem>> = [
[
{
label: 'View',
icon: 'i-lucide:eye',
},
{
label: 'Copy',
icon: 'i-lucide:copy',
},
{
label: 'Edit',
icon: 'i-lucide:pencil',
},
],
[
{
label: 'Delete',
color: 'error' as const,
icon: 'i-lucide:trash',
},
],
];
</script>
<template>
<PContextMenu
:items="items"
:pohon="{ content: 'w-48' }"
>
<div class="border-border-accented text-sm border rounded-md border-dashed flex w-72 aspect-video items-center justify-center">
Right click here
</div>
</PContextMenu>
</template>
With custom slot
Use the slot property to customize a specific item.
You will have access to the following slots:
#{{ item.slot }}#{{ item.slot }}-leading#{{ item.slot }}-label#{{ item.slot }}-trailing
<script setup lang="ts">
import type { PPContextMenuItem } from 'pohon-ui';
import { ref } from 'vue';
const loading = ref(true);
const items = [
{
label: 'Refresh the Page',
slot: 'refresh' as const,
},
{
label: 'Clear Cookies and Refresh',
},
{
label: 'Clear Cache and Refresh',
},
] satisfies Array<PPContextMenuItem>;
</script>
<template>
<PContextMenu
:items="items"
:pohon="{ content: 'w-48' }"
>
<div class="border-border-accented text-sm border rounded-md border-dashed flex w-72 aspect-video items-center justify-center">
Right click here
</div>
<template #refresh-label>
{{ loading ? 'Refreshing...' : 'Refresh the Page' }}
</template>
<template #refresh-trailing>
<PIcon
v-if="loading"
name="i-lucide:loader-circle"
class="text-primary shrink-0 size-5 animate-spin"
/>
</template>
</PContextMenu>
</template>
#item, #item-leading, #item-label and #item-trailing slots to customize all items.Extract shortcuts
Use the extractShortcuts utility to automatically define shortcuts from menu items with a kbds property. It recursively extracts shortcuts and returns an object compatible with defineShortcuts.
<script setup lang="ts">
const items = [
[{
label: 'Show Sidebar',
kbds: ['meta', 'S'],
onSelect() {
console.log('Show Sidebar clicked');
}
}, {
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'D'],
onSelect() {
console.log('Show Toolbar clicked');
}
}, {
label: 'Collapse Pinned Tabs',
disabled: true
}],
[{
label: 'Refresh the Page'
}, {
label: 'Clear Cookies and Refresh'
}, {
label: 'Clear Cache and Refresh'
}, {
type: 'separator' as const
}, {
label: 'Developer',
children: [[{
label: 'View Source',
kbds: ['option', 'meta', 'U'],
onSelect() {
console.log('View Source clicked');
}
}, {
label: 'Developer Tools',
kbds: ['option', 'meta', 'I'],
onSelect() {
console.log('Developer Tools clicked');
}
}], [{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'C'],
onSelect() {
console.log('Inspect Elements clicked');
}
}], [{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'J'],
onSelect() {
console.log('JavaScript Console clicked');
}
}]]
}]
];
defineShortcuts(extractShortcuts(items));
</script>
select function of the corresponding item.API
Props
| Prop | Default | Type |
|---|---|---|
size | 'md' | "md" | "xs" | "sm" | "lg" | "xl" |
items | T | |
checkedIcon | appConfig.pohon.icons.check | string | objectThe icon displayed when an item is checked. |
loadingIcon | appConfig.pohon.icons.loading | string | objectThe icon displayed when an item is loading. |
externalIcon | true | string | false | true | objectThe icon displayed when the item is an external link.
Set to |
content | AContextMenuContentProps & Partial<EmitsToProps<AMenuContentEmits>>The content of the menu.
| |
portal | true | string | false | true | HTMLElementRender the menu in a portal.
|
labelKey | 'label' | keyof Extract<NestedItem<T>, object> | DotPathKeys<Extract<NestedItem<T>, object>>The key used to get the label from the item. |
descriptionKey | 'description' | keyof Extract<NestedItem<T>, object> | DotPathKeys<Extract<NestedItem<T>, object>>The key used to get the description from the item. |
disabled | boolean | |
modal | true | boolean The modality of the dropdown menu. When set to |
pressOpenDelay | 700 | numberThe duration from when the trigger is pressed until the menu opens. |
pohon | { content?: ClassValue; viewport?: ClassValue; group?: ClassValue; label?: ClassValue; separator?: ClassValue; item?: ClassValue; itemLeadingIcon?: ClassValue; itemLeadingAvatar?: ClassValue; itemLeadingAvatarSize?: ClassValue; itemTrailing?: ClassValue; itemTrailingIcon?: ClassValue; itemTrailingKbds?: ClassValue; itemTrailingKbdsSize?: ClassValue; itemWrapper?: ClassValue; itemLabel?: ClassValue; itemDescription?: ClassValue; itemLabelExternalIcon?: ClassValue; } |
Slots
| Slot | Type |
|---|---|
default | object |
item | { item: NestedItem<T>; active?: boolean | undefined; index: number; pohon: object; } |
item-leading | { item: NestedItem<T>; active?: boolean | undefined; index: number; pohon: object; } |
item-label | { item: NestedItem<T>; active?: boolean | undefined; index: number; } |
item-description | { item: NestedItem<T>; active?: boolean | undefined; index: number; } |
item-trailing | { item: NestedItem<T>; active?: boolean | undefined; index: number; pohon: object; } |
content-top | { sub: boolean; } |
content-bottom | { sub: boolean; } |
Emits
| Event | Type |
|---|---|
update:open | [payload: boolean] |
Theme
Below is the theme configuration skeleton for the PContextMenu. Since the component is provided unstyled by default, you will need to fill in these values to apply your own custom look and feel. If you prefer to use our pre-built, opinionated styling, you can instead use our UnoCSS preset, this docs is using it as well.
export default defineAppConfig({
pohon: {
contextMenu: {
slots: {
content: '',
viewport: '',
group: '',
label: '',
separator: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatar: '',
itemLeadingAvatarSize: '',
itemTrailing: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: '',
itemWrapper: '',
itemLabel: '',
itemDescription: '',
itemLabelExternalIcon: ''
},
variants: {
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
active: {
true: {
item: '',
itemLeadingIcon: ''
},
false: {
item: '',
itemLeadingIcon: ''
}
},
loading: {
true: {
itemLeadingIcon: ''
}
},
size: {
xs: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
sm: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
md: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
lg: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
xl: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
}
}
},
compoundVariants: [],
defaultVariants: {
size: 'md'
}
}
}
};
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import pohon from 'pohon-ui/vite'
export default defineAppConfig({
pohon: {
contextMenu: {
slots: {
content: '',
viewport: '',
group: '',
label: '',
separator: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatar: '',
itemLeadingAvatarSize: '',
itemTrailing: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: '',
itemWrapper: '',
itemLabel: '',
itemDescription: '',
itemLabelExternalIcon: ''
},
variants: {
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
active: {
true: {
item: '',
itemLeadingIcon: ''
},
false: {
item: '',
itemLeadingIcon: ''
}
},
loading: {
true: {
itemLeadingIcon: ''
}
},
size: {
xs: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
sm: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
md: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
lg: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
},
xl: {
label: '',
item: '',
itemLeadingIcon: '',
itemLeadingAvatarSize: '',
itemTrailingIcon: '',
itemTrailingKbds: '',
itemTrailingKbdsSize: ''
}
}
},
compoundVariants: [],
defaultVariants: {
size: 'md'
}
}
}
};
Akar
With Pohon UI, you can achieve similar component functionality with less code and effort, as it comes with built-in styles mechanism and behaviors that are optimized for common use cases. Since it's using unocss-variants it adds a runtime cost, but it can be worth it if you prioritize development speed and ease of use over fine-grained control.
If this is a deal breaker for you, you can always stick to using Akar and build your own custom components on top of it.