You have probably needed a diagonal hero, a circular avatar, or a notched label and reached for extra markup or image assets. clip-path lets you cut any element into a shape in pure CSS. By the end of this guide you will build a stylish profile card with a slanted banner, a perfectly circular avatar mask, a notched “New” tag, and a chevron on the button , all shaped with clip-path, no SVG or extra wrappers needed.
Why clip-path Matters
clip-path defines a visible region for an element. Everything outside the path is hidden, while layout and clicks still use the element’s original box. You can use basic shapes like circle(), ellipse(), and inset(), or define any polygon with a list of points. This approach reduces DOM complexity, replaces image masks, and keeps your components themeable with CSS variables. It plays well with background images, gradients, and pseudo-elements, and you can animate it for tasteful microinteractions. Compared to images or SVG filters, clip-path ships no extra assets and can be swapped or tuned right in your stylesheet.
Prerequisites
You only need core CSS skills and a bit of comfort with variables and pseudo-elements. The project is approachable and shows the value of clip-path in real UI work.
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
The card uses a banner for the header area, an image for the avatar, a content block with name and role, a button with a chevron, and a decorative tag in the corner. Keep the HTML clean; we will handle all shapes in CSS.
<!-- HTML -->
<div class="card">
<div class="banner" aria-hidden="true"></div>
<img class="avatar" src="avatar.jpg" alt="Photo of Jamie Doe" />
<div class="content">
<h2 class="title">Jamie Doe</h2>
<p class="meta">Frontend Engineer</p>
<button class="cta" type="button">
Message
<span class="visually-hidden">Send a message to Jamie Doe</span>
</button>
</div>
<span class="tag" aria-hidden="true">New</span>
</div>
Step 2: The Basic CSS & Styling
Set up color variables, type, spacing, and the card shell. The banner uses a gradient, and the avatar will sit partially over the banner. We will keep the DOM lean and rely on positioning where needed.
/* CSS */
:root {
--bg: #0f172a;
--card: #0b1220;
--elev: #111a2e;
--text: #e5e7eb;
--muted: #94a3b8;
--accent: #6366f1;
--accent-2: #22d3ee;
--ring: rgba(99, 102, 241, 0.4);
}
*,
*::before,
*::after { box-sizing: border-box; }
html, body {
height: 100%;
}
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Inter, Arial, sans-serif;
color: var(--text);
background: radial-gradient(1200px 600px at 20% 0%, #0b1327 0%, var(--bg) 60%);
display: grid;
place-items: center;
padding: 32px;
}
.card {
position: relative;
width: 380px;
background: linear-gradient(180deg, var(--card), #0a101e);
border: 1px solid #152138;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 6px 30px rgba(0,0,0,.35), 0 0 0 1px rgba(255,255,255,.03) inset;
}
.banner {
height: 160px;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.avatar {
position: absolute;
top: 92px;
left: 24px;
width: 96px;
height: 96px;
object-fit: cover;
border: 4px solid var(--card);
background: #1f2937;
}
.content {
padding: 24px;
padding-left: 140px; /* space for avatar */
padding-top: 32px;
}
.title {
margin: 0 0 6px;
font-size: 22px;
line-height: 1.2;
}
.meta {
margin: 0 0 18px;
color: var(--muted);
}
.cta {
position: relative;
display: inline-grid;
place-items: center;
gap: 8px;
padding: 12px 16px 12px 16px;
color: #0b1220;
background: #e5e7eb;
border: 0;
cursor: pointer;
font-weight: 600;
transition: background .2s ease, color .2s ease;
}
.cta:focus-visible {
outline: 3px solid var(--ring);
outline-offset: 3px;
}
.tag {
position: absolute;
top: 12px;
right: 12px;
padding: 6px 10px;
font-size: 12px;
letter-spacing: .06em;
text-transform: uppercase;
background: #111b31;
color: #cbd5e1;
}
/* A11y utility */
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
Advanced Tip: Store colors and spacing in CSS variables. clip-path is just a shape; theming still relies on your tokens. This keeps the same geometry while you change mood or brand with one palette swap.
Step 3: Building the Diagonal Banner
Time to cut the banner on a diagonal. We will use a polygon with four points. Coordinates are given in percentages relative to the element box, which makes the shape responsive.
/* CSS */
.banner {
height: 160px;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
clip-path: polygon(
0% 0%,
100% 0%,
100% 65%,
0% 100%
);
transition: clip-path .5s cubic-bezier(.2,.8,.2,1);
}
.card:hover .banner {
clip-path: polygon(
0% 0%,
100% 0%,
100% 55%,
0% 90%
);
}
How This Works (Code Breakdown)
clip-path: polygon(…) defines a series of points that connect to form a closed shape. The banner still occupies a rectangle for layout, but the polygon hides the lower-right portion to produce a dramatic slant. Points run clockwise: top-left (0 0), top-right (100% 0), a point at 100% 65% to drop the right edge, then 0 100% to finish the diagonal. The hover state nudges the lower points up by 10% to create a subtle shift on interaction without any reflow.
Using percentages keeps the slant proportional across screen densities and zoom levels. You can add more points for zigzags or waves, or fewer points for trapezoids. If you need a clean wave divider, see how a dedicated component stitches points into a smooth shape in the tutorial on making a CSS wave page divider. That pattern and this polygon use the same coordinate system and mental model.
The transition applies to clip-path, which is animatable. Small morphs like this are light and give the UI a bit of life. For larger morphs with many points, keep an eye on performance and reduce the duration or point count.
Step 4: Building the Circular Avatar and Notched Tag
Next, cut the avatar into a perfect circle and notch the corner label. The circle() function is straightforward and reads well. The label uses another polygon to bite out a triangle.
/* CSS */
/* Perfect circular mask for any rectangular image */
.avatar {
position: absolute;
top: 92px;
left: 24px;
width: 96px;
height: 96px;
object-fit: cover;
border: 4px solid var(--card);
background: #1f2937;
clip-path: circle(46% at 50% 50%);
}
/* Notched "ticket" tag shape */
.tag {
position: absolute;
top: 12px;
right: 12px;
padding: 6px 10px 6px 14px; /* extra room for notch side */
font-size: 12px;
letter-spacing: .06em;
text-transform: uppercase;
background: #111b31;
color: #cbd5e1;
clip-path: polygon(
0% 0%,
100% 0%,
100% 100%,
14px 100%,
8px 50%,
14px 0%
);
transition: clip-path .3s ease;
}
.card:hover .tag {
/* tighten the notch on hover */
clip-path: polygon(
0% 0%,
100% 0%,
100% 100%,
18px 100%,
10px 50%,
18px 0%
);
}
/* Button chevron made with a clipped pseudo-element */
.cta {
position: relative;
display: inline-grid;
grid-auto-flow: column;
align-items: center;
gap: 10px;
padding: 12px 18px 12px 16px;
color: #0b1220;
background: #e5e7eb;
border: 0;
cursor: pointer;
font-weight: 600;
clip-path: inset(0 round 14px);
transition: background .2s ease, color .2s ease, clip-path .3s ease;
}
.cta::after {
content: "";
width: 12px;
height: 12px;
background: currentColor;
/* Right-pointing triangle */
clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
transition: transform .2s ease;
}
.cta:hover {
background: #ffffff;
}
.cta:hover::after {
transform: translateX(2px);
}
/* Progressive enhancement & fallback */
@supports not (clip-path: circle(50%)) {
.avatar { clip-path: none; border-radius: 50%; }
}
@media (prefers-reduced-motion: reduce) {
.banner, .tag, .cta::after { transition: none; }
}
How This Works (Code Breakdown)
circle(46% at 50% 50%) clips the avatar into a circle sized to 46% of the smallest dimension, centered in the middle. You can express the radius as a length (like 48px) or a percentage. If you want to compare this approach with a traditional technique, see how to make a circle with CSS using border-radius. clip-path gives the same visual result but is unambiguous about shape geometry and combines neatly with other functions in the same component.
The tag polygon creates a notch on the left side by pulling a point into the center (8px 50%). The polygon walks around the rectangle, detours inward to carve the notch, then closes. Using px for the notch ensures a crisp edge on all screen sizes. You can flip the notch to the right by mirroring x-coordinates, or animate it for microfeedback on hover like the example above.
The button chevron uses a 12px square pseudo-element clipped into a right-facing triangle. That approach mirrors the classic border-only triangle hack, and if you want a refresher on that technique, check the step-by-step for a triangle right. clip-path keeps the geometry legible in one property and lets you nudge, rotate, or scale the shape without fragile border math. The button itself uses clip-path: inset(0 round 14px) for rounded corners, which you can animate for playful corner morphs.
If you enjoy organic silhouettes, morphing a polygon with 6-8 points produces natural blobs without images. For a deeper dive into that shape style, the tutorial on a CSS blob organic shape shows practical point layouts and easing choices you can reuse here.
Advanced Techniques: Adding Animations & Hover Effects
clip-path is animatable for small, tasteful effects. Keep the point count low for smooth frames. Below, the button corners pulse with inset() round radii, and the banner slant breathes on card focus to draw attention. A motion query disables these for users who prefer less motion.
/* CSS */
/* Animated corners on the button using inset() round */
@keyframes blobCorners {
0% { clip-path: inset(0 round 14px 22px 14px 22px); }
50% { clip-path: inset(0 round 22px 14px 22px 14px); }
100% { clip-path: inset(0 round 14px 22px 14px 22px); }
}
.cta.cta--alive {
animation: blobCorners 3.5s ease-in-out infinite;
}
/* Draw attention to the card on focus within */
.card:focus-within .banner {
clip-path: polygon(0% 0%, 100% 0%, 100% 58%, 0% 92%);
}
/* Respect motion preferences */
@media (prefers-reduced-motion: reduce) {
.cta.cta--alive { animation: none; }
.card:focus-within .banner { clip-path: polygon(0% 0%, 100% 0%, 100% 65%, 0% 100%); }
}
inset() with round radii produces rounded corners without border-radius and is fully animatable. Swapping radii across axes gives a soft, breathing feel. The banner morph is the same polygon move from earlier, triggered on focus-within for keyboard users.
Accessibility & Performance
clip-path changes paint, not layout. That separation has a few consequences that you must keep in mind for a high quality component.
Accessibility
Decorative shapes should not be announced. The banner and tag carry aria-hidden because they add style, not information. The avatar has an alt attribute with a human-friendly label. The button contains a visually hidden text node to clarify the action for assistive tech.
Focus visibility matters when shapes change. The button uses a strong outline for contrast. If you clip a shape to a point, check that the focus ring is not cut off by overflow: hidden on ancestors. For animated shapes, honor prefers-reduced-motion and stop morphing if requested. When a clipped element is interactive, the hit box still uses the original rectangle. If you want the hit box to match the shape, use pointer-events on children or a dedicated interactive element sized to the visible area.
Performance
clip-path is drawn by the compositor in modern engines and is fast for simple shapes. Animations remain smooth when you animate a small number of points, or when you animate shapes like circle() or inset() round radii. Large polygons with dozens of points or heavy shadows can be more costly. In those cases, reduce point count or use transforms for large moves and clip-path for the final cut. For safety, guard complex shapes with @supports and provide a border-radius or rectangular fallback, as shown for the avatar.
Note: Safari and iOS support polygon(), circle(), ellipse(), and inset() broadly. If you support very old browsers, consider adding -webkit-clip-path for legacy versions, or rely on border-radius in @supports fallbacks.
Build Shapes That Work Hard
You shaped a diagonal banner, a circular avatar, a notched tag, and a button chevron with clip-path, then added subtle motion with hover and keyframe animation. The same techniques scale to headers, cards, menus, and dividers without extra DOM or image assets. Now you have the tools to sculpt UI with math and variables, and to ship clean, flexible components that carry your brand shape language.