Context menu VContextMenu opens a menu at the pointer on a right click, a long press on Android, the Menu key or Shift+F10. Its menu slot receives target, the element the menu was opened on, so one menu serves a whole list.
Usage Right-click a file, or focus it and press Shift+F10.
<script setup lang="ts">
import { ref } from 'vue'
import { VContextMenu, VMenuItem, VMenuSeparator, VTypography } from 'vectis-ui'
const files = ['report.pdf', 'budget.xlsx', 'notes.txt']
const last = ref('')
function run(command: string, target: Element | null) {
const file = target?.closest<HTMLElement>('[data-file]')?.dataset.file
last.value = file ? `${command} ${file}` : command
}
</script>
<template>
<div class="column">
<VContextMenu as="ul" class="files" aria-label="Files">
<li v-for="file in files" :key="file" :data-file="file">
<button type="button" class="file">{{ file }}</button>
</li>
<template #menu="{ target }">
<VMenuItem label="Open" @select="run('Open', target)" />
<VMenuItem label="Rename" @select="run('Rename', target)" />
<VMenuSeparator />
<VMenuItem label="Delete" tone="danger" @select="run('Delete', target)" />
</template>
</VContextMenu>
<VTypography variant="body-sm" tone="muted">
{{ last ? `Last command: ${last}.` : 'Right-click a file, or focus it and press Shift+F10.' }}
</VTypography>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-3);
}
.files {
display: grid;
gap: var(--vectis-space-1);
max-inline-size: 20rem;
margin: 0;
padding: var(--vectis-space-2);
border: 1px dashed var(--vectis-color-border);
border-radius: var(--vectis-radius-md);
list-style: none;
}
.file {
inline-size: 100%;
padding: var(--vectis-space-2);
border: none;
border-radius: var(--vectis-radius-sm);
background: var(--vectis-color-surface-muted);
color: var(--vectis-color-text);
font: inherit;
text-align: start;
}
</style> Examples The commands are VMenu items, so groups, separators, submenus, size and compact work the same. Near an edge of the viewport the menu flips to stay on screen.
<script setup lang="ts">
import { VContextMenu, VMenuItem, VMenuSeparator } from 'vectis-ui'
</script>
<template>
<VContextMenu class="zone" tabindex="0">
Right-click this area.
<template #menu>
<VMenuItem label="Paste" />
<VMenuItem label="Sort by">
<template #submenu>
<VMenuItem label="Name" selected />
<VMenuItem label="Date modified" />
<VMenuItem label="Size" />
</template>
</VMenuItem>
<VMenuSeparator />
<VMenuItem label="New folder" />
</template>
</VContextMenu>
</template>
<style scoped>
.zone {
display: grid;
place-items: center;
min-block-size: 8rem;
border: 1px dashed var(--vectis-color-border);
border-radius: var(--vectis-radius-md);
color: var(--vectis-color-text-muted);
}
</style> Shift with a right click shows the browser's menu. disabled gives the whole zone back to it.
<script setup lang="ts">
import { ref } from 'vue'
import { VContextMenu, VMenuItem, VSwitch } from 'vectis-ui'
const disabled = ref(false)
</script>
<template>
<div class="column">
<VSwitch v-model="disabled" label="Use the browser's menu" />
<VContextMenu :disabled="disabled" class="zone" tabindex="0">
Right-click this area, with or without Shift.
<template #menu>
<VMenuItem label="Copy" />
<VMenuItem label="Paste" />
</template>
</VContextMenu>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-3);
}
.zone {
display: grid;
place-items: center;
min-block-size: 8rem;
border: 1px dashed var(--vectis-color-border);
border-radius: var(--vectis-radius-md);
color: var(--vectis-color-text-muted);
}
</style> Knowing whether it is open v-model:open follows every opening and closing. Opened from code, the menu appears under the focused element of the zone, or under the zone itself. Keep every command reachable another way too, a visible button for instance: iOS Safari has no long press menu, and nothing tells a reader the menu exists.
Open from code The menu is closed.
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VContextMenu, VMenuItem, VTypography } from 'vectis-ui'
const open = ref(false)
</script>
<template>
<div class="column">
<VContextMenu v-model:open="open" class="zone" tabindex="0">
Right-click this area.
<template #menu>
<VMenuItem label="Copy" />
<VMenuItem label="Paste" />
</template>
</VContextMenu>
<div class="row">
<VButton variant="ghost" tone="neutral" @click="open = true">Open from code</VButton>
<VTypography variant="body-sm" tone="muted">
The menu is {{ open ? 'open' : 'closed' }}.
</VTypography>
</div>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-3);
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--vectis-space-3);
}
.zone {
display: grid;
place-items: center;
min-block-size: 8rem;
border: 1px dashed var(--vectis-color-border);
border-radius: var(--vectis-radius-md);
color: var(--vectis-color-text-muted);
}
</style> API Props Slots