Using a CSS safe triangle to prevent accidental submenu switching

Post on X
Copy URL
Share

Menu UIs on the web can benefit from a technique known as a safe triangle. When front-end engineers account for it during implementation, they can improve the usability of nested menus for mouse users.

When users move diagonally toward a target submenu, the pointer often crosses another menu item, causing the submenu to switch before they reach it. To avoid this, users must first move horizontally toward the submenu and then move up or down.

▲ A nested menu without a safe triangle. On the left, diagonal pointer movement activates an item along the way. On the right, moving horizontally and then vertically keeps the original item active.

What is a safe triangle?

To solve this at the implementation level, draw a triangle between the pointer position and the submenu’s top-left and bottom-left corners. Extending the hit area in this way prevents another menu item from activating while the pointer moves diagonally.

▲ With a safe triangle, the other menu item marked ① does not activate.

This technique is also known as a “safe area,” “safe polygon,” menu-aim, or “prediction cone.”

Creating a triangle with CSS clip-path

Combining CSS clip-path: polygon() with CSS custom properties provides a simple way to implement a safe triangle. The following demo visualizes the safe triangle. Toggle it on and off, then try moving the pointer diagonally.

Note: The safe triangle is disabled on mobile devices for the reasons described later. Please view this article in a desktop browser.

Defining the triangle with clip-path: polygon()

The pseudo-element uses position: fixed, so it is positioned in viewport coordinates. Its rectangular position and dimensions are supplied through CSS custom properties, and clip-path clips it into a triangle.

.action-item {
  /* A triangle connecting the pointer to the submenu's upper and lower edges. */
  &::before {
    content: "";
    position: fixed;
    /* Place it behind the trigger and submenu. */
    z-index: 2;
    top: var(--safe-top);
    left: var(--safe-left);
    width: var(--safe-width);
    height: var(--safe-height);
    /* Hidden by default so it is active only while hovered. */
    display: none;
    clip-path: polygon(
      0 var(--safe-y, 50%), /* ① */
      100% 0,  /* ② */
      100% 100% /* ③ */
    );
  }
}

▲ The triangular pseudo-element.

The ::before pseudo-element is rectangular, but clip-path clips it into the safe triangle. Only the area inside the clipped triangle receives pointer events (CSS Masking Module Level 1).

Passing the pointer position to CSS custom properties

On every mousemove event, JavaScript passes the pseudo-element’s enclosing rectangle—its position, width, and height—and the position of the vertex nearest the pointer to CSS custom properties.

▲ The starting point is near the pointer on the trigger. The other two points are the submenu’s top-left and bottom-left corners.

trigger.addEventListener("mousemove", (event) => {
  // Calculate the triangle from the positioned submenu's bounding box.
  // (Omitted)

  // Slightly overlap the starting point so the pointer does not leave the triangle at its edge.
  item.style.setProperty("--safe-top", `${menuRect.top}px`);
  item.style.setProperty("--safe-left", `${safeLeft}px`);
  item.style.setProperty("--safe-width", `${Math.max(menuRect.left - safeLeft, 0)}px`);
  item.style.setProperty("--safe-height", `${menuRect.height}px`);
  item.style.setProperty("--safe-y", `${safeY}px`);
});

Enabling the safe triangle only for mouse input

A safe triangle is useful only for hover interactions. To avoid placing the triangle on touch-only mobile and tablet devices, display the pseudo-element only when the media query matches.

.action-item {
  @media (any-hover: hover) and (any-pointer: fine) {
    &:hover::before {
      /* Enable the transparent triangular hit area. */
      display: block;
    }
  }
}

The conditions mean the following:

  • (any-hover: hover)

    • At least one available input device supports hover.
  • (any-pointer: fine)

    • At least one available input device has a fine pointer capable of precise targeting.

Using the Popover API and interestfor

So far, CSS has tracked the :hover and focus states to toggle the submenu. With HTML and CSS features available as of 2026, the implementation can be made a little simpler.

Use the popovertarget attribute to open the submenu from a click or tap, and the interestfor attribute to open it from hover or focus. When both attributes reference the same popover="hint" element, the submenu can open and close across different input methods.

See the following demo.

<!-- Reference the same id for click/tap and hover/focus. -->
<button
  popovertarget="share-submenu"
  interestfor="share-submenu"
>Share</button>
<ul id="share-submenu" popover="hint">
  <!-- Submenu items -->
</ul>

CSS anchor positioning is used to place the submenu.

A popover cannot be measured until after it opens. JavaScript therefore stores the most recent pointer position, waits for the toggle event to confirm that the popover is open, and then updates the triangle.

For more details about the interestfor attribute, see Using HTML command and interestfor to reduce JavaScript for modals and tooltips.

Examples

Mega menu

A safe triangle can also be used in a UI with categories on the left and corresponding lists on the right.

Opening downward

The same approach also works for submenus placed below the trigger.

Libraries make implementation easier

Implementing this from scratch may sound like a lot of work. Fortunately, some UI libraries already support safe triangles.

Floating UI

The React package for Floating UI includes the safePolygon() function and the useHover() hook. Used together, they create a dynamic polygon during hover interactions. Floating UI calls this a safe polygon.

▼ Implementation excerpt

// `context` is returned by `useFloating()`.
const hover = useHover(context, {
  mouseOnly: true,
  // Also block hover on other items behind the pointer path.
  handleClose: safePolygon({ blockPointerEvents: true }),
});
const { getReferenceProps, getFloatingProps } =
  useInteractions([hover]);

safePolygon() also considers the pointer’s direction and creates a polygon only when needed.

React Aria

Adobe’s React Aria is a headless UI library for building accessible interfaces. Among headless UI libraries, it is arguably one of the most successful—and one of the few Adobe-developed web libraries to achieve such broad adoption. It also provides behavior similar to a safe triangle.

▼ Implementation excerpt

<SubmenuTrigger delay={0}>
  {/* The first child is the parent item, and the second is the submenu. */}
  <MenuItem id="share">Share</MenuItem>
  <Popover>
    <Menu>
      <MenuItem id="email">Email</MenuItem>
      <MenuItem id="sms">SMS</MenuItem>
    </Menu>
  </Popover>
</SubmenuTrigger>

Unlike the CSS approach, React Aria does not create a triangle in the DOM. Instead, JavaScript uses the pointer’s direction together with a timeout, keeping the parent item open while the pointer is moving toward the submenu.

This article introduced a transparent hit area built with a pseudo-element. Because that approach can still misclassify pointer movement, React Aria appears to have explored a JavaScript-based method to improve accuracy.

In other words, there is no single way to implement a safe triangle. It is a surprisingly deep technique that has inspired many different approaches.

Other libraries, including MUI and shadcn/ui, also support safe triangles because the submenu libraries they use internally provide the behavior.

Safe triangles have existed for decades

The safe triangle concept dates back decades. It was used in hierarchical menus on the Mac in the mid-1980s. An article looking back at the Mac’s interface design uses the phrase “a buffer zone shaped like a <” to describe it.

On the web, a 2013 article analyzing Amazon’s mega menu drew attention. The jQuery plugin jQuery-menu-aim, released alongside it, also became widely used and helped popularize safe triangles.

Conclusion

When working with HTML and CSS, it is easy to think only in rectangular boxes. A diagonal hit area is therefore easy to overlook. Mobile-first design has been standard for years, but mouse input remains useful in many contexts, including tablets that can be used with a mouse.

Even if it seems like a small improvement, consider using a safe triangle on the web to make menus easier to use.

Share on social media
Your shares help us keep the site running.
Post on X
Copy URL
Share
IKEDA Yasunobu

CEO of ICS, part-time lecturer at the University of Tsukuba, and editor-in-chief of ICS MEDIA. He specializes in visual programming and UI design projects such as ClockMaker Labs.

Articles by this staff