Spring Context Menu
Right-click menu that springs out of the corner nearest the pointer, clamps itself inside the viewport and flies a submenu out sideways.
About This Component
A right-click context menu that opens where the pointer is and springs out of the corner nearest it, so the panel always reads as coming from the cursor rather than appearing on top of it. It clamps itself inside the viewport on both axes, staggers its rows in, slides a single highlight between them, and flies a submenu out to whichever side has room.
Read the full effect overview
A single self-contained component: one HTML file, one stylesheet, one script, vanilla JavaScript and core GSAP, no framework and no plugins. Ships in three looks, vault, brutal and soft, switched with one data-variant attribute.
What's Included
- Opens at the pointer on right-click, and on a 480ms long-press on touch
- Spring pop from the corner nearest the cursor, with the transform origin picked per open
- Clamped inside the viewport on both axes, so a corner click flips the panel instead of clipping it
- Rows stagger in, and one highlight slides between them rather than a background switching on
- Submenu flies out sideways and flips to the other side near the right edge
- Full keyboard path: arrow keys move, ArrowRight opens the submenu, Escape closes
- Three shipped variants: vault, brutal, soft
Perfect For
- File and asset managers where every row needs the same actions
- Canvas and editor tools that replace the browser menu
- Dashboards and data tables with per-row operations
- Any app that wants a native-feeling menu without a component library
How It Works
Spring from the pointer
On contextmenu the panel is unhidden and measured with offsetWidth and offsetHeight, which are layout values and so are never distorted by the tween that follows. Whether the panel has to flip on each axis decides both its clamped position and its transformOrigin, then one gsap.fromTo scales it from 0.55 on a back.out(2.6) ease so it grows out of the corner under the cursor.
One sliding highlight
The highlight keeps left:0 and top:0 in CSS and takes its whole box from the hovered row's offsetLeft, offsetTop, offsetWidth and offsetHeight, so nothing doubles up with a CSS inset. It fades in on the first row and tweens x, y, width and height between rows after that; focus drives it as well as hover, so the keyboard path looks identical.
Submenu placement
The submenu is a child of the panel rather than of its row, which keeps every row unpositioned and leaves the panel as the single offset parent for measuring. Its top comes from the parent row's offsetTop, its side is chosen by testing the panel's clamped left against the viewport, and it flies in from that side with a back.out ease while its own rows stagger.