Skip to content

Intent Safe Menu

A mega-menu that reads where the pointer is heading, so cutting the corner never steals the submenu.

DOM + CSS0 deps1 file · 9 KB TSX

Prominimaleditorial
Preview

Props

Intent guard
320
Show intent path

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useRef, useState } from "react";

type Section = { label: string; items: string[] };

type Sample = { x: number; y: number; t: number };

const DEFAULT_SECTIONS: Section[] = [
  { label: "Outerwear", items: ["Shell parka", "Wool topcoat", "Field jacket", "Down liner"] },
  { label: "Knitwear", items: ["Fisherman rib", "Merino crew", "Cashmere half-zip"] },
  { label: "Trousers", items: ["Pleated wool", "Carpenter twill", "Selvedge denim", "Drawstring"] },
  { label: "Footwear", items: ["Service boot", "Derby", "Trail runner"] },
  { label: "Objects", items: ["Card holder", "Tote", "Watch cap", "Belt"] },
];

/** Signed area of the triangle a-b-p: its sign says which side of ab p is on. */
function sideOf(ax: number, ay: number, bx: number, by: number, px: number, py: number) {

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this animation

This is the fix for the single most common defect in hover menus. Switching the open panel on mouseenter seems obviously correct until you notice that moving from a section in the list to an item in the panel travels diagonally across every section below it, each of which grabs the panel on the way past — so the thing you were reaching for disappears. The solution is to read intent rather than position: on each enter, build a triangle from the pointer's previous location to the two corners of the panel's near edge, and if the pointer currently sits inside that wedge, it is travelling toward the open panel and the switch is deferred. Three supporting details decide whether it actually works. It needs two pointer samples, because a single point has no direction, and the older one is promoted on a timer so it sits far enough behind to define a heading. The deferred switch must commit anyway after a short tolerance, and to whichever section the pointer is over when the timer fires — without that, a cursor parked inside the wedge freezes the menu permanently, which is worse than the original bug. And the guard must never apply to focus or arrow keys, which have no trajectory at all. The wedge is drawn while a switch is being held, so the behaviour is visible rather than mysterious.

Curator’s note

Turn the guard off and cut diagonally across the list — the defect is instant, and it is in almost every mega-menu you have used.

Pairs well with

Pro

Pendulum Menu

animations