Home / Snippets / UI Components /
Notched corner
Cut geometric notches from element corners using clip-path: polygon() — no images, no gradients, pure CSS.
Quick implementation
.notched {
--notch: 1rem; /* adjust notch depth */
clip-path: polygon(
var(--notch) 0%, 100% 0%,
100% 100%, 0% 100%,
0% var(--notch)
);
}
/* Double notch — top-left and top-right */
.notched--double {
--notch: 1rem;
clip-path: polygon(
var(--notch) 0%, calc(100% - var(--notch)) 0%,
100% var(--notch), 100% 100%,
0% 100%, 0% var(--notch)
);
}
/* All four corners */
.notched--all {
--notch: 1rem;
clip-path: polygon(
var(--notch) 0%, calc(100% - var(--notch)) 0%,
100% var(--notch), 100% calc(100% - var(--notch)),
calc(100% - var(--notch)) 100%, var(--notch) 100%,
0% calc(100% - var(--notch)), 0% var(--notch)
);
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer implementing notched-corner UI elements.
Goal: Apply geometric corner cuts (notches) to cards and buttons using
clip-path: polygon() — no SVG, no images, no gradient hacks.
Technical constraints:
- Define a --notch custom property (e.g. --notch: 1rem) on the element
so the cut depth is easy to adjust in one place.
- Use clip-path: polygon(...) with absolute percentage + calc() points
to describe the shape. For a single top-left notch on a rectangle:
polygon(var(--notch) 0%, 100% 0%, 100% 100%, 0% 100%, 0% var(--notch))
- For each additional corner, add two extra polygon points (the cut-in
and cut-out) following the clockwise winding order.
- Use oklch() for any colors — no hex or rgba values.
- Use CSS custom properties (var(--card), var(--text), etc.) for theming.
Variants to implement:
A) .notched--single — top-left corner only (5 polygon points)
B) .notched--double — top-left and top-right (6 points)
C) .notched--all — all four corners, producing an octagon (8 points)
Framework variant (pick one):
1) Vanilla CSS utility classes as above.
2) React component — accept a corners prop ("single" | "double" | "all")
and a notchSize prop (string, e.g. "1rem"); apply the correct
clip-path via inline style so the value is dynamic.
Edge cases to handle:
- clip-path clips box-shadow — if a shadow is required, wrap the element
in a parent and apply drop-shadow() via filter: drop-shadow() instead.
- Percentage-based clip-path points scale with the element, so the notch
depth in rem stays fixed while the polygon scales correctly.
- Avoid mixing border-radius with clip-path on the same element — they
do not compose; the clip-path wins and rounds corners are ignored.
- When using clip-path on interactive elements (buttons, links), confirm
that the visible clipped area still matches the clickable hit region;
clip-path does restrict pointer-events to the clipped shape.
Return CSS only (or a React component if variant 2 is chosen).
Why clip-path beats gradient hacks
Before clip-path had broad browser support, developers faked notched corners using layered linear-gradient() backgrounds — typically two gradients composited with background-origin and precise background-size values. That approach breaks the moment the element changes size, requires separate gradient values for each corner, and falls apart entirely on non-white backgrounds or elements placed over images. It is also effectively unmaintainable: adjusting the notch depth means recalculating multiple pixel values by hand.
clip-path: polygon() defines the actual geometric shape of the element. The polygon points use a mix of percentage-based coordinates (so the shape scales with the element) and calc() expressions that reference a --notch custom property (so the cut depth stays in absolute units). Change one CSS variable and every notched element updates instantly. No gradients, no image assets, no JavaScript.
Support for clip-path with percentage polygon points is now universal across modern browsers, making this the correct approach in 2026.
Calculating polygon points
A standard rectangle traced clockwise in CSS coordinates (origin top-left) has four corners: 0% 0%, 100% 0%, 100% 100%, 0% 100%. To notch the top-left corner, replace the single point 0% 0% with two points that describe the diagonal cut:
var(--notch) 0%— the point where the cut meets the top edge0% var(--notch)— the point where the cut meets the left edge
The full single-notch polygon then becomes five points. For each additional corner you want to notch, the same substitution applies — replace one corner point with two, adding one extra point per notched corner. Four notched corners produce an octagon with eight points.
The calc(100% - var(--notch)) expressions handle the opposing edges. For example, notching the top-right corner needs the cut to meet the top edge at calc(100% - var(--notch)) 0% and the right edge at 100% var(--notch). Following clockwise order consistently prevents self-intersecting paths that would produce unexpected fills.
The box-shadow trade-off
clip-path is a compositing operation that removes anything outside the polygon — including the element's own box-shadow. Shadows are rendered outside the element's border box, so clip-path clips them away entirely. This is the most common pitfall when adopting notched corners.
The solution is to move the shadow to a wrapper element using filter: drop-shadow(). Unlike box-shadow, drop-shadow() is applied as a post-compositing filter on the element's alpha channel — it follows the actual clipped shape, producing a correctly notched shadow. The syntax is similar: filter: drop-shadow(0 4px 12px oklch(0 0 0 / 0.4)).
The same logic applies to outline and border-radius: both are overridden by clip-path. If a focus ring is needed for accessibility on a clipped button, use :focus-visible with a box-shadow on the wrapper, or accept that the focus ring will follow the clipped polygon shape (which is often visually acceptable and still meets WCAG 2.4.11 requirements for focus appearance).