You need a clipping shape that goes beyond polygon points and rounded corners. You want a reusable, responsive, designer-friendly shape that you can draw once and apply across components, images, and sections. By the end of this article you will clip content with an SVG-defined shape using clip-path: url(…), wire up an external SVG that ships in its own file, and add a tasteful hover animation that transforms the shape itself.
Why SVGs as a clip-path URL Matter
CSS provides built-in shape functions like circle(), ellipse(), and polygon(). These cover common needs, but they hit a wall when you want a complex contour, an irregular blob, a diagonal slice with rounded joins, or a brand mark outline. An SVG clip path solves this cleanly. You can draw the shape in a vector tool, keep its math in one place, and reference it by ID. It scales without artifacts, it is easy to share across components, and it plays well with responsive layouts. You also gain the option to host the shape once in a separate file and cache it across pages.
Prerequisites
We will keep the markup lean and use modern CSS. You only need a few basics to follow along:
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
We will build two cards that showcase the technique. The first card uses an inline SVG-defined clip path. The second card references an external SVG file. Each card holds an image and a caption, and the inline SVG lives next to the content so its ID can be unique per instance.
<!-- HTML -->
<section class="stage">
<figure class="card card-blob">
<img class="media" src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1200&auto=format&fit=crop" alt="Mountains at sunrise">
<figcaption class="caption">Inline SVG clip-path (Blob)</figcaption>
<!-- Inline defs ensure the ID is unique and scannable per component -->
<svg class="clip-defs" width="0" height="0" aria-hidden="true" focusable="false">
<defs>
<!-- Use objectBoundingBox so the path scales with the target box.
The transform trick maps 0-100 coordinates to 0-1. -->
<clipPath id="clip-blob-1" clipPathUnits="objectBoundingBox">
<path id="clip-blob-1-path"
transform="scale(0.01)"
d="M50,2
C70,5 98,20 98,45
C98,70 80,98 52,98
C25,98 2,78 2,50
C2,25 30,3 50,2 Z" />
</clipPath>
</defs>
</svg>
</figure>
<figure class="card card-diagonal">
<img class="media" src="https://images.unsplash.com/photo-1519681393784-d120267933ba?q=80&w=1200&auto=format&fit=crop" alt="Books and a desk lamp">
<figcaption class="caption">External SVG clip-path (Diagonal slice)</figcaption>
</figure>
</section>
Step 2: The Basic CSS & Styling
We will define a few custom properties for sizing and spacing, set up a simple stage grid, and give each card a consistent base. The card itself receives the clip-path, so the image and caption both clip as a unit. We also add a fallback when clip-path is not supported.
/* CSS */
:root {
--card-w: min(38ch, 88vw);
--gap: 2rem;
--radius: 18px;
--caption-bg: hsl(0 0% 0% / 0.55);
--caption-fg: white;
--shadow: 0 10px 30px hsl(0 0% 0% / 0.2);
}
*,
*::before,
*::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
line-height: 1.5;
color: #1b1b1b;
background: linear-gradient(180deg, #f8fafc, #eef2f7);
display: grid;
place-items: start center;
min-height: 100svh;
padding-block: 6vh;
}
.stage {
display: grid;
gap: var(--gap);
grid-template-columns: repeat(auto-fit, minmax(var(--card-w), 1fr));
width: min(1200px, 96vw);
padding-inline: 2vw;
}
.card {
position: relative;
width: 100%;
aspect-ratio: 4 / 3;
overflow: clip; /* hide any bleed if clip-path is absent at load */
box-shadow: var(--shadow);
border-radius: var(--radius); /* graceful fallback shape */
background: #ddd;
isolation: isolate; /* keep caption overlay effects inside */
}
.media {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 0.75rem 1rem;
color: var(--caption-fg);
background: var(--caption-bg);
backdrop-filter: blur(3px);
font-weight: 600;
}
/* Inline SVG clip-path enabled on the blob card */
.card-blob {
/* The ID lives in the same document */
clip-path: url(#clip-blob-1);
}
/* External clip-path will be defined in Step 4 */
/* Progressive enhancement: if clip-path url() is unsupported, keep the rounded box */
@supports not (clip-path: url(#foo)) {
.card {
overflow: hidden;
}
}
Advanced Tip: Custom properties make it easy to theme multiple cards from a single place. You can expose per-component variables (like –radius or –shadow) and change them per card, or even per section, to create subtle visual variety without rewriting rules.
Step 3: Building the Inline SVG clip-path
Now we wire up the first component. The blob card already contains an inline <clipPath> with id=”clip-blob-1″. We only need a small set of rules to make the clip path reactive and add a gentle hover effect.
/* CSS */
.card-blob {
clip-path: url(#clip-blob-1);
transition: clip-path 200ms ease, transform 300ms ease;
}
/* Keep the defs out of layout but styleable */
.card-blob .clip-defs { position: absolute; width: 0; height: 0; }
/* Animate the clipping shape itself by transforming the path in defs */
.card-blob:hover .clip-defs #clip-blob-1-path {
transform-origin: 50% 50%;
transform: scale(1.04) rotate(-2deg);
transition: transform 300ms cubic-bezier(.2,.7,.2,1);
}
/* A tiny image zoom to complement the shape motion */
.card-blob:hover .media {
transform: scale(1.03);
transition: transform 300ms cubic-bezier(.2,.7,.2,1);
}
How This Works (Code Breakdown)
The key is clip-path: url(#clip-blob-1). A fragment-only URL looks up an element by ID in the current document. That element must be an SVG clipPath, which can contain shapes like path, polygon, or even a circle. Since the definition sits inside the same component, we avoid global ID collisions, keep everything portable, and make the shape easy to tweak next to the template.
clipPathUnits=”objectBoundingBox” tells the browser that the shape’s coordinates are relative to the target’s box, not absolute pixels. Coordinates in that system range from 0 to 1. Drawing a smooth blob directly in 0-1 space is tedious. The transform=”scale(0.01)” trick lets you author the path with a 0-100 mental model and then scale it down by 100x. You can paste exported paths from your vector editor, rescale once, and move on.
We put the svg.clip-defs at width/height 0 to keep it out of layout, but it remains in the DOM and can accept styles. That gives us a neat move: on .card-blob:hover, we target the path inside the defs and apply a transform that rotates and scales the clipping shape. The media zoom complements the effect and sells the motion. If you only need simple cutouts like a circle or rectangle, the CSS shape functions are quicker, and you can revisit the step-by-step guides for how to make a circle with CSS or triangles such as how to make a triangle up with CSS. For organic curves, an SVG blob gives you far more freedom. If you want more blob approaches, see how to make a CSS blob.
Step 4: Using an External SVG File as a clip-path URL
Now we switch to a shared clip library. This is handy when you want to use the same shape across many pages or components and let the browser cache it. We will create clips.svg with one or more clip paths, then reference it with a fragment URL.
/* CSS */
.card-diagonal {
/* Same-origin file recommended. Use an absolute or relative path you host. */
clip-path: url("clips.svg#diagonal-slice");
}
/* A little polish on hover to prove it is live */
.card-diagonal:hover .media {
transform: translateY(-1%) scale(1.02);
transition: transform 300ms ease;
}
<!-- HTML (the external file: clips.svg) -->
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<!-- Normalized 0-1 coordinates, easy to reuse at any size -->
<clipPath id="diagonal-slice" clipPathUnits="objectBoundingBox">
<polygon points="0,0 1,0 1,0.85 0,1"></polygon>
</clipPath>
<!-- Another example: a ticket notch -->
<clipPath id="ticket" clipPathUnits="objectBoundingBox">
<path d="
M0,0 H1 V0.7
C0.9,0.7 0.85,0.8 0.85,0.9
C0.85,1 0.9,1 1,1 H0
C0.1,1 0.15,1 0.15,0.9
C0.15,0.8 0.1,0.7 0,0.7 Z" />
</clipPath>
</defs>
</svg>
How This Works (Code Breakdown)
clip-path accepts url(“FILE.svg#id”) with a fragment that points to the clipPath element in that file. The file must be on the same origin, or you must serve appropriate CORS headers. If you reference a different origin without CORS, the URL will fail silently and the element will render unclipped.
Using clipPathUnits=”objectBoundingBox” makes each shape flexible by default. The same path adapts to any target size, whether your card is 4:3, a square thumbnail, or a full-width banner. You can include multiple clipPath elements in clips.svg and name them for your design system. Designers can update the file without touching your components. The browser will cache the file like any static asset.
Serving tips: use a far-future cache header and stable file name when the shapes are versioned. Prefer a short path and keep IDs readable. Avoid duplicate IDs across separate SVG files when you bundle later. If you need data URIs for special cases, most browsers accept data:image/svg+xml urls with a fragment, but keep an eye on URL encoding and size limits in stylesheets.
Advanced Techniques: Animations & Hover Effects
You can animate the clipping path with CSS transforms applied to shapes inside the defs. Transforms apply in the SVG coordinate space, so set a sane transform-origin. For subtle motion, scale or rotate the path on hover, and complement that with a slight image translation. If you need path morphs, SVG’s native <animate> on the d attribute works well in most modern browsers.
/* CSS */
@keyframes blob-breathe {
0% { transform: scale(1) rotate(0deg); }
50% { transform: scale(1.03) rotate(-1deg); }
100% { transform: scale(1) rotate(0deg); }
}
/* Idle micro-motion */
.card-blob .clip-defs #clip-blob-1-path {
transform-origin: 50% 50%;
animation: blob-breathe 6s ease-in-out infinite;
}
/* Respect motion preferences */
@media (prefers-reduced-motion: reduce) {
.card-blob .clip-defs #clip-blob-1-path,
.card-blob .media,
.card-diagonal .media {
animation: none !important;
transition: none !important;
transform: none !important;
}
}
/* Optional: a one-off attention hover */
.card-diagonal:hover {
filter: drop-shadow(0 6px 18px hsl(220 30% 20% / 0.25));
}
Animating the element behind the clip is cheaper than morphing the path. Use that for most interactions. Reserve path transforms for small movements and low-frequency effects. If you need synchronized hero animations, consider keyframing a single shape in defs and reusing it across instances so the GPU work stays predictable.
Accessibility & Performance
Accessibility
Clipping shapes change the visual silhouette, not semantics. Make sure your content remains understandable without the effect. If the clipped element is decorative, add aria-hidden=”true” on the element or mark it as presentation when you use SVG images. For meaningful photos, write alt text that conveys the subject rather than its shape. If the shape communicates state or action, pair it with text or an accessible name.
Respect user preferences for motion. The prefers-reduced-motion media query above disables shape transforms and image zooms. If you plan to morph paths or run continuous animations, provide a clear pause or turn-off control where practical.
Performance
clip-path is fast on modern engines. The clip is evaluated on the compositor in many cases, which keeps scrolling and transforms smooth. That said, very complex paths, especially animated ones, add work. Keep your d data tight, simplify curves where you can, and avoid animating the path shape on every frame. Scaling or rotating the clip is cheaper than morphing it.
When using an external file, you get caching benefits. Place your shared clips in a single SVG, serve it with strong caching headers, and reference shapes by ID. Avoid forcing layout thrash around clipped elements; your card styles already contain the element and reduce overdraw. Resist adding will-change: clip-path; it can trigger extra memory use. Test with the performance panel and watch paint flashing to confirm that the effect is not causing unnecessary rasterization.
Keep Shapes in One Place, Reuse Everywhere
You built two clip-path setups: one inline for per-component tweaks and one external for shared, cached shapes. You learned how objectBoundingBox units make shapes responsive, how to scale authored paths down to normalized units, and how to animate the path for a light touch of motion. Now you have a pattern you can drop into any card, hero, or image grid and the tools to grow your own clip library.