Skip to content

Context Menu

Use mode="context" to open a menu from a trigger’s context gesture. The trigger remains the menu’s anchor while scrolling.

Choose an action from the context menu.

Code

The following example assumes that you have imported the component and set up the theme.

Context Menu
<div class="context-menu-example">
<sv-menu class="context-menu" mode="context">
<sv-flyout offset="0">
<button type="button" class="context-target" slot="trigger">
<span class="file-icon" aria-hidden="true">PDF</span>
<span>
<strong>Quarterly report.pdf</strong>
<small>Right-click, touch and hold, or press SHIFT+F10</small>
</span>
</button>
<button type="button" class="context-option" role="menuitem">Open</button>
<button type="button" class="context-option" role="menuitem">Rename</button>
<button type="button" class="context-option" role="menuitem">Make a copy</button>
<div class="divider" role="separator"></div>
<button type="button" class="context-option danger" role="menuitem">Move to trash</button>
</sv-flyout>
</sv-menu>
<p class="context-result" aria-live="polite">Choose an action from the context menu.</p>
</div>
<script type="module">
/** @type {Menu} */
const menu = document.querySelector(".context-menu-example .context-menu");
/** @type {HTMLElement} */
const result = document.querySelector(".context-menu-example .context-result");
/** @type {NodeListOf<HTMLButtonElement>} */
const options = menu.querySelectorAll(".context-option");
for (const option of options) {
option.addEventListener("click", () => {
result.textContent = `${option.textContent} selected.`;
menu.close();
});
}
</script>
95 collapsed lines
<style>
.context-menu-example {
display: grid;
gap: 0.75em;
max-inline-size: 32rem;
margin: auto;
sv-flyout::part(content) {
min-block-size: 100px;
}
.context-target {
display: flex;
gap: 1em;
align-items: center;
inline-size: 100%;
padding: 1em;
font: inherit;
color: var(--sv-gray-2);
text-align: start;
touch-action: manipulation;
cursor: context-menu;
user-select: none;
background: var(--sv-gray-7);
border: var(--sv-bdr-sz-sm) dashed var(--sv-gray-4);
border-radius: var(--sv-bdr-rad-lg);
-webkit-touch-callout: none;
&:focus-visible {
outline: var(--sv-bdr-sz-md) solid var(--sv-accent);
outline-offset: 0.2em;
}
span:last-child {
display: grid;
gap: 0.25em;
}
small {
color: var(--sv-gray-3);
}
}
.file-icon {
display: grid;
flex: none;
place-items: center;
inline-size: 3.25rem;
block-size: 4rem;
font-size: 0.75rem;
font-weight: bold;
color: var(--sv-accent-content);
background: var(--sv-accent);
border-radius: var(--sv-bdr-rad-sm);
}
.context-option {
padding: 0.4em 0.75em;
font: inherit;
color: var(--sv-gray-2);
text-align: start;
cursor: pointer;
background: transparent;
border: 0;
border-radius: var(--sv-bdr-rad-sm);
&:focus-visible {
outline: var(--sv-bdr-sz-sm) solid var(--sv-accent);
outline-offset: -0.15em;
}
&.danger {
color: var(--sv-error);
}
@media (hover: hover) and (pointer: fine) {
&:hover {
background: var(--sv-gray-5);
}
}
}
.divider {
block-size: 1px;
background: var(--sv-gray-5);
}
.context-result {
min-block-size: 1.5em;
margin: 0;
color: var(--sv-gray-3);
text-align: center;
}
}
</style>