Clipping Alone Can’t Build Every Tooltip Notch
CSS clipping and masking have been viable techniques for years now, with solid browser support. But when a tooltip needs a small arrow that stays fixed in size while the rest of the tooltip stretches, clip-path on its own starts to fall short. A recent project involving tooltips over in-text links exposed the limitations—and pointed to a more flexible solution.
Two tooltip designs were needed, depending on whether the content was plain text or an image. The text-only version is straightforward: a pseudo-element positioned at the bottom creates the notch, and because the background is a flat color, no clipping tricks are required. The image version is the harder case, and there are several ways to approach it.
Why polygon clipping Stops Short
The most obvious approach is CSS clip-path with a custom polygon(). Using percentage-based coordinates keeps the clip responsive, and calc() values let the triangle notch keep its size while the rest of the clip stretches with the parent. For simple shapes, that solution is clean and adequate.
The limitation appears when the notch is not a straight triangle but a more elaborate custom shape. A polygon path can’t represent that cleanly, and exporting an SVG clipping path does not fix the core issue. With an SVG path referenced via url(#clipPathId), the arrow is part of the same path that defines the rectangle, so both stretch together. Change the tooltip’s aspect ratio and the arrow distorts along with the container.
Stacking Masks for Independent Scaling
The key insight is that mask-image supports multiple layers, much like background-image does. Instead of one unified path, the final mask can be composed of independent layers that scale on their own terms.
Two layers do the job:
- A large rectangle covering the whole block except for a stripe along the bottom.
- A separate image of the arrow that sits in that stripe.
Because the rectangle and the arrow are independent mask layers, the rectangle can stretch freely to match the tooltip’s dimensions while the arrow keeps its fixed, original ratio at all times.
Three mask properties define those layers, mirroring the way background layers are configured:
mask-imagedraws the rectangle via a linear gradient and the arrow via inline SVG.mask-positionanchors the arrow at center-bottom; the rectangle needs no explicit position since it starts at the top-left.mask-repeatmust be disabled on both layers; otherwise, the repeating linear gradient would cover the entire element.
The technique holds up when content or dimensions change: swap the image or resize the tooltip, and the bottom arrow remains undistorted.
Going Further with Rounded Corners
The same layer-stacking principle scales to more elaborate tooltip shapes. An iMessage-style tooltip, for example, requires rendering rounded corners and an offset arrow rather than a simple rectangular body.
That tooltip needs more mask layers, each handling one part of the final shape:
- Four radial gradients, one circle per corner.
- A horizontal rectangle to fill the top and bottom spans.
- A vertical rectangle covering the middle section.
- An SVG layer for the arrow.
The logic stays the same even though the code grows. Corners come from four radial gradients; the body is assembled from one vertical and one horizontal rectangle; the arrow is its own inline SVG layer. Flipping the arrow and moving its position yields left- or right-aligned variants. The approach also works on plain-background tooltips, though those simpler cases rarely justify this much CSS when a single pseudo-element notch suffices.



