Context Menu
Use mode="context" to open a menu from a trigger’s context gesture. The trigger remains the menu’s anchor while scrolling.
Code
The following example assumes that you have imported the component and set up the theme.
<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>