Skip to content

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.

Choose an action from the context menu.

Code

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

File Context Menu
<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>