File Context Menu
A file row that opens its context menu on right-click, touch and hold, or Shift+F10 while focused. The row itself is the flyout's trigger, so the open menu stays anchored to it while the page scrolls. --sv-flyout-offset: 0 lets the menu open flush at the pointer instead of a step away from it. Each option reports the chosen action to an aria-live line before closing the menu.
Code
The following example assumes that you have imported the components and set up the theme.
<div class="file-context-menu-example">19 collapsed lines
<sv-menu class="context-menu" mode="context"> <sv-flyout aria-label="File actions menu"> <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="application/x-typescript" module>10 collapsed lines
const menu = document.querySelector<Menu>(".file-context-menu-example .context-menu")!; const result = document.querySelector<HTMLElement>(".file-context-menu-example .context-result")!; const options = menu.querySelectorAll<HTMLButtonElement>(".context-option");
for (const option of options) { option.addEventListener("click", () => { result.textContent = `${option.textContent} selected.`; menu.close(); }); }</script>
<style>92 collapsed lines
.file-context-menu-example { display: grid; gap: 0.75em; max-inline-size: 32rem; margin: auto;
sv-flyout { --sv-flyout-offset: 0;
&::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;
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>