You want crisp angles, diagonal sections, and custom badges without extra wrappers or raster images. The polygon() function in clip-path gives you pixel-sharp, vector-like shapes that respond to the element box. In this tutorial you will learn how polygon() works, how to think in coordinates, and how to build two practical components: a responsive angled hero and a five-point star badge. You will leave with patterns you can re-use anywhere you need geometric precision.
Why Understanding polygon() in clip-path Matters
Developers often reach for extra markup, pseudo-elements, and border tricks to fake angles and notches. The polygon() function replaces those hacks with a single, declarative mask. It is modern, highly controllable, and plays well with responsive layouts because percentage coordinates scale with the element. For triangles you might have used the border method; that still works, and you can see a classic approach in this guide on how to make a triangle with CSS. polygon() covers that case and many beyond it. For round shapes, clip-path also supports circle() and ellipse(), but for perfect circles there is also border-radius; if you want a refresher on radius-based approaches, check the tutorial on how to make a circle with CSS. polygon() shines when you need straight edges, precise points, and component-level masks without images or SVG.
Prerequisites
You do not need a graphics editor. You only need a mental model for percentage-based coordinates and standard CSS skills. A code playground helps for quick tweaks.
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
We will build one hero section with a diagonal bottom edge, and one product card with a star badge. The markup remains minimal. Classes identify the two components we will style with clip-path: polygon(). The badge lives inside the card, positioned in the corner.
<!-- HTML -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>clip-path: polygon() demos</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
</head>
<body>
<main class="wrap">
<section class="hero clip-hero">
<h1>Angles with clip-path: polygon()</h1>
<p>Resize the window to see the diagonal adapt with percentage coordinates.</p>
</section>
<section class="grid">
<article class="card">
<div class="badge clip-star" aria-hidden="true">New</div>
<h2>Sample Product</h2>
<p>This card uses a polygon-masked badge. The star is pure CSS.</p>
<button class="cta">Buy now</button>
</article>
</section>
</main>
</body>
</html>
Step 2: The Basic CSS & Styling
Start with a small design system: colors, spacing, and type. The hero and card will both inherit from these variables. The grid creates breathing room. None of the next rules rely on clip-path yet; this layer sets the stage so the geometry changes remain easy to read.
/* CSS */
:root{
--bg: #0f1226;
--surface: #161a36;
--ink: #e6e8ff;
--muted: #b7baf2;
--accent: #7c5cff;
--accent-2: #01dbc6;
--radius: 16px;
--gap: 24px;
}
*{ box-sizing: border-box; }
html, body { height: 100%; }
body{
margin: 0;
font: 16px/1.55 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
color: var(--ink);
background: radial-gradient(1200px 60% at 80% 0%, #1a1e44, var(--bg) 60%);
}
.wrap{
width: min(1100px, 92%);
margin: 0 auto;
padding-bottom: 64px;
}
h1, h2{ line-height: 1.2; margin: 0 0 12px; }
p{ margin: 0 0 12px; color: var(--muted); }
.grid{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--gap);
margin-top: 40px;
}
.card{
position: relative;
background: linear-gradient(180deg, #202451, var(--surface));
border: 1px solid rgba(255,255,255,.08);
border-radius: var(--radius);
padding: 24px 20px 20px;
box-shadow: 0 10px 30px rgba(0,0,0,.25);
}
.cta{
display: inline-block;
margin-top: 8px;
padding: 10px 14px;
color: #0d0f20;
background: linear-gradient(180deg, var(--accent-2), #05b6a6);
border: none;
border-radius: 10px;
font-weight: 700;
cursor: pointer;
}
.cta:hover{ filter: brightness(1.05); }
Advanced Tip: Keep color and spacing in variables. When you revisit clip-path coordinates later, you will not dig through unrelated styles. Shape tuning stays focused, and swapping themes remains small and safe.
Step 3: Building the Angled Hero Mask
The hero uses a trapezoid mask created by polygon(). Four points define a slanted bottom edge, and the rest of the section stays visible. Percentages make the angle responsive, so the slope holds across screen sizes without media queries.
/* CSS */
.clip-hero{
position: relative;
isolation: isolate; /* contain pseudo-elements if you add them later */
min-height: 280px;
margin-top: 28px;
padding: 48px 28px 80px;
background:
radial-gradient(900px 100% at 0% 0%, rgba(124,92,255,.35), transparent 60%),
linear-gradient(120deg, #1f2b62, #192044 60%, #121735 100%);
border-radius: var(--radius);
box-shadow: 0 30px 80px rgba(0,0,0,.35);
/* The key line: top-left, top-right, bottom-right, bottom-left */
clip-path: polygon(0% 0%, 100% 0%, 100% 78%, 0% 100%);
}
/* Subtle overlay to add texture inside the clipped area */
.clip-hero::after{
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(800px 100% at 20% 10%, rgba(1,219,198,.12), transparent 50%),
linear-gradient(180deg, rgba(255,255,255,.06), transparent 40%);
pointer-events: none;
mix-blend-mode: screen;
border-radius: inherit;
clip-path: inherit; /* match hero clip so the overlay follows the same shape */
}
/* Headings inside hero */
.clip-hero h1{
font-size: clamp(28px, 5vw, 44px);
letter-spacing: -0.02em;
}
.clip-hero p{ font-size: 18px; }
/* A small hover demo to prove the mask changes smoothly */
.clip-hero{
transition: clip-path .4s cubic-bezier(.2,.6,.2,1);
}
.clip-hero:hover{
clip-path: polygon(0% 0%, 100% 0%, 100% 72%, 0% 92%);
}
/* Optional tweak for very narrow screens */
@media (max-width: 520px){
.clip-hero{
clip-path: polygon(0% 0%, 100% 0%, 100% 84%, 0% 100%);
padding-bottom: 64px;
}
}
How This Works (Code Breakdown)
clip-path defines the visible region of the element. With polygon(), you pass a list of points as x y pairs. The browser connects the points in order and closes the shape back to the first point. In this hero we specify four points: top-left (0% 0%), top-right (100% 0%), a lowered bottom-right (100% 78%), and bottom-left (0% 100%). The bottom edge tilts because the right corner sits higher than the left.
Percentages refer to the element’s reference box. For clip-path on standard elements the reference is the border box. 0% 0% is the top-left corner of the element, and 100% 100% is the bottom-right. You can use decimals and even values greater than 100% or negative values to push corners past the box, which creates dramatic overhangs.
We set clip-path on ::after as inherit so the overlay shares the same mask. That keeps lighting and noise inside the same trapezoid without recalculating points. The hover transition adjusts the two bottom coordinates. Notice that the first two points remain fixed; only the lower edge moves, so the effect reads as a sliding angle rather than a squish.
polygon() also excels at simple triangles. If you want a refresher on non-clip-path triangle tricks, see this guide to make a triangle with CSS. When you move to polygon(), you gain direct control over every corner instead of relying on borders and transparent sides.
Step 4: Building the Star Badge with evenodd
Stars cross their own lines, so the fill rule matters. polygon() accepts an optional first argument for the fill rule: nonzero (default) or evenodd. For a five-point star, evenodd gives a clean star without unexpected wedges. We will attach this badge to the top-right of the card.
/* CSS */
.card{
/* already defined in Step 2 */
padding-top: 36px; /* give room for the badge overlap */
}
.badge{
--size: 84px;
position: absolute;
top: -20px;
right: -14px;
width: var(--size);
height: var(--size);
display: grid;
place-items: center;
color: #0c0f25;
font: 700 14px/1 system-ui, -apple-system, Segoe UI, Roboto, Arial;
text-transform: uppercase;
letter-spacing: .05em;
background: conic-gradient(from -45deg, #ffe37a, #ffc857 40%, #ffb547 70%, #ffe37a 100%);
box-shadow: 0 12px 30px rgba(0,0,0,.35);
transform: rotate(-6deg);
}
/* Five-point star using evenodd fill */
.clip-star{
clip-path: polygon(evenodd,
50% 0%,
61.8% 38.2%,
100% 38.2%,
68% 61.8%,
79% 100%,
50% 76%,
21% 100%,
32% 61.8%,
0% 38.2%,
38.2% 38.2%
);
transition: clip-path .4s cubic-bezier(.2,.6,.2,1), transform .25s ease;
}
/* Hover: morph the inner points outward for a starburst look */
.card:hover .clip-star{
clip-path: polygon(evenodd,
50% 2%,
66% 32%,
98% 34%,
70% 64%,
82% 98%,
50% 74%,
18% 98%,
30% 64%,
2% 34%,
34% 32%
);
transform: rotate(-2deg) scale(1.02);
}
How This Works (Code Breakdown)
The badge uses absolute positioning to overlap the card and a conic gradient for a metallic feel. The core piece is polygon(evenodd, …points…). evenodd treats self-crossings as holes, then alternates fill on each crossing. That yields the classic five-point star shape without extra markup. The points use the golden ratio values (38.2 and 61.8) for a pleasing proportion.
We animate between two polygon() lists. clip-path values are interpolable in modern engines when both lists have the same number of points. Keep the count and order consistent across states for smooth morphs. The hover state pushes the inner vertices outward and tweaks rotation for a subtle celebration effect. If you need a step-by-step shape recipe with pseudo-elements instead of clip-path, you can also study this guide on how to make a 5-point star with CSS; then bring the same coordinates mindset back to polygon().
One subtle detail: clip-path trims hit testing, so the mouse only interacts with the visible star. That often matches user intent for badges and notches, and it reduces accidental clicks around transparent corners.
Advanced Techniques: Adding Animations & Hover Effects
clip-path morphs are readable when the change is small and the number of points remains stable. You can also animate the shape over time to add motion to hero sections. Keep the range tight so content does not jump.
/* CSS */
/* Morph the hero angle gently, respecting motion preferences */
@media (prefers-reduced-motion: no-preference){
.clip-hero{
animation: hero-wave 8s ease-in-out infinite;
}
}
@keyframes hero-wave{
0% { clip-path: polygon(0% 0%, 100% 0%, 100% 78%, 0% 100%); }
50% { clip-path: polygon(0% 0%, 100% 0%, 100% 74%, 0% 92%); }
100% { clip-path: polygon(0% 0%, 100% 0%, 100% 78%, 0% 100%); }
}
/* Badge sparkle using a moving gradient; mask stays constant */
@media (prefers-reduced-motion: no-preference){
.badge{
background-size: 200% 200%;
animation: shine 3.6s linear infinite;
}
}
@keyframes shine{
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
Accessibility & Performance
Do not treat clip-path as a visual-only trick. It changes the clickable area and can affect the way users navigate content. Respect motion settings, provide clear text contrast inside clipped areas, and think about focus outlines when shapes are interactive.
Accessibility
Decorative shapes should not be announced. The badge uses aria-hidden=”true” to stay silent for assistive tech. If a clipped area is a button or link, keep visible focus by avoiding overflow clipping of the outline, or add a custom focus style inside the shape. For animated masks, gate them behind prefers-reduced-motion. The hero animation above already does this. If a shape communicates a state change, label the control and do not rely on geometry alone.
Performance
clip-path is fast for moderate shapes. The browser rasterizes the element and applies a GPU-accelerated mask. Simple polygons with up to a few dozen points animate well on modern devices. Intensive patterns that animate many complex elements at once can drop frames. Prefer fewer points, smaller areas, and short transitions. Avoid animating large text blocks with frequent reflows inside clipped containers. When you need a fallback, wrap styles with a feature query and provide a simpler shape that does not depend on masking.
/* CSS */
/* Fallback example: drop to a rounded rectangle when clip-path is unsupported */
@supports not (clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%)){
.clip-hero, .clip-star{
clip-path: none;
}
.clip-hero{ border-radius: var(--radius); }
.badge{ border-radius: 12px; }
}
Keep Your Geometry Toolbox Close
You built a responsive diagonal hero and a morphing star badge using clip-path: polygon(). You learned the coordinate system, the evenodd fill rule, and how to animate shape points with care. Now you have the vocabulary to sketch any straight-edged shape and apply it to real components. The next time you need a badge, notch, ribbon, or angular cut, reach for polygon() first and shape the interface with code.