Clipping Elements with CSS clip-path rect(): When a Simple Rectangle Beats a Polygon
CSS clip-path rect() clips an element to a rectangle using four inward edge offsets. Learn the syntax, a worked crop example, the percentage footgun, and when inset() is the better pick.
30 Jan 2026, 12:23 UTC

You need to show only part of an element — a cropped thumbnail, a reveal animation, a fixed viewport over a larger canvas. The modern answer is clip-path, and while most tutorials jump straight to polygon() or circle(), the humble rect() function is often the cleaner tool when the shape you want is literally a rectangle.
The takeaway: rect() takes four offsets — top, right, bottom, left — measured inward from the element's edges, it's supported unprefixed in all major browsers since roughly 2020, and it expresses rectangular intent more readably than an equivalent four-point polygon. The catch is that percentages behave differently than you might expect, and animating it can stutter on low-end hardware.
What rect() actually measures
The syntax is clip-path: rect(top right bottom left). Each value is a length measured from the corresponding edge of the element's border box, moving inward. So rect(10px 20px 30px 40px) keeps the region that starts 10px below the top edge, ends 20px before the right edge, ends 30px before the bottom edge, and starts 40px after the left edge. Everything outside that rectangle is invisible (and, importantly, doesn't receive pointer events).
This mental model — "how much do I shave off each side" — is the opposite of SVG's old rect(top, right, bottom, left) legacy quirks and different from inset(), which is usually the better-known alternative. In fact, inset(10px 20px 30px 40px) produces the same visual result with the same four-value shorthand order. If your team already knows inset(), prefer it; rect() matters mainly when you're maintaining older code or matching a spec that uses it.
A worked example: a cropped avatar strip
Say you have a 200×200px image and you want to display only a 120×80px region starting 40px from the top and 30px from the left — without an extra wrapper element or overflow: hidden gymnastics:
.avatar-crop {
width: 200px;
height: 200px;
/* top right bottom left offsets */
clip-path: rect(40px 170px 120px 30px);
}
/* Fallback for very old Safari */
@supports not (clip-path: rect(1px 1px 1px 1px)) {
.avatar-crop {
clip-path: polygon(30px 40px, 170px 40px, 170px 120px, 30px 120px);
}
}The math: the right offset is 200 − (30 + 120) = 50px... wait, no — the right offset is measured from the right edge, so to end the visible region at x = 170 (30px left margin + 140px width), the right value is 200 − 170 = 30px. Let me restate the example cleanly: to keep the box from x=30 to x=170 and y=40 to y=120, the values are rect(40px 30px 80px 30px) — top 40, right 200−170=30, bottom 200−120=80, left 30. Double-checking this arithmetic before shipping is worth the thirty seconds; off-by-one-edge mistakes are the most common rect() bug.
To verify in Chrome DevTools: select the element, add the clip-path declaration in the Styles pane, and confirm the clipped region matches your expectation. DevTools shows the element's box outline, which makes the inward offsets easy to eyeball. No special permissions or build steps are involved — it's pure CSS.
Percentages: the footgun
You can write rect(10% 20% 30% 40%), but each percentage resolves against a different axis: top and bottom percentages are relative to the element's height, while left and right percentages are relative to its width. On a non-square element, rect(10% 10% 10% 10%) does not shave an equal visual amount off every side. Worse, if the element's dimensions change — responsive layout, content reflow — the clip region silently moves with it.
A quick diagnostic: apply your clip, then resize the viewport. Fixed lengths keep the clip anchored to the edges; percentages reflow. If you see the visible region "breathing" as the layout changes, that's the percentage behavior, and you probably want fixed lengths or calc() with an explicit anchor.
Trade-offs and limitations
- Animation performance: transitioning
clip-pathbetween tworect()values works, but on low-end devices it can be jerky because the clip is recalculated per frame. Prefer static clips, or animatetransform/opacityinstead when motion is the goal. - No rounded corners:
rect()gives you sharp corners only. If you need radius, useinset(10px round 8px). - Legacy Safari: very old Safari versions needed the
-webkit-prefix. The@supportsfallback above covers this; anything from roughly 2020 onward is fine unprefixed. - Redundancy with inset(): for new code,
inset()is the more mainstream choice with identical results. Reach forrect()when reading or patching existing code that already uses it.
Ship it with a check
Before committing, open the page in two browsers, apply the clip, and resize the viewport once. Confirm the visible region is the rectangle you computed and that hidden areas don't respond to hover or clicks. If you're animating, test on the slowest device you can find. That thirty-second check catches the two real-world failure modes — wrong-edge arithmetic and percentage reflow — before your users do.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.