Lesson 34 • Advanced Track
CSS Shapes & Clip-Path
By the end of this lesson you'll be able to cut any element into a circle, hexagon or arrow with clip-path, animate those clips for reveal effects, wrap paragraphs around shapes with shape-outside, and fade an image edge with a CSS mask — the toolkit behind every "how did they do that?" layout.
What You'll Learn
float and transition before. If positioning still feels shaky, review Advanced Positioning first.💡 Think of It Like This
clip-path is a cookie cutter. You press a shaped cutter onto a sheet of dough and only the dough inside the cutter stays — but the whole sheet is still on the table underneath. The element keeps its full rectangle for layout and clicks; you've only changed what shows through.
shape-outside is a stone dropped in a stream. The water (your text) doesn't flow through the stone — it bends and hugs the outline as it passes. And a mask is a stencil with a gradient: where the stencil is solid the paint lands fully, where it fades the paint fades too, so you can dissolve an edge into nothing.
1. The Four Basic Shapes
clip-path defines a clipping region: everything inside the shape is visible, everything outside is hidden (but still in the DOM). You build that region with a shape function. Four cover almost everything:
| Function | Shape | Example |
|---|---|---|
circle() | Round, radius + centre | circle(50% at 50% 50%) |
ellipse() | Oval, two radii | ellipse(40% 60% at 50% 50%) |
inset() | Rectangle pulled in from edges | inset(10% round 12px) |
polygon() | Any shape from x% y% points | polygon(50% 0%, 0% 100%, 100% 100%) |
Run the worked example. Each tile is an ordinary <div> with a background colour — only the clip-path line differs. Read the comments to see how each function maps to a shape.
Worked example: circle, ellipse, inset, polygon
One div, four different clip-path functions
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>clip-path basics</title>
<style>
body { font-family: system-ui, sans-serif; padding:24px; background:#fafafa; }
.grid { display:flex; flex-wrap:wrap; gap:24px; justify-content:center; }
/* Every tile is a plain 150x150 box. clip-path decides what shows. */
.tile {
width:150px; height:150px; display:flex; align-items:center;
justify-content:center; color:white; font-weight:700; font-size:.8rem;
...2. polygon() — The Coordinate Workhorse
polygon() is the one you'll reach for most. Each point is an x% y% pair measured from the element's own box: 0% 0% is the top-left corner, 100% 100% the bottom-right. List the points in order around the outline — walk the perimeter clockwise — and the browser joins them with straight lines.
That single rule gives you hexagons, stars, arrows and diagonal section edges. The worked example below annotates a hexagon point by point so you can see the walk.
Worked example: a hexagon, point by point
Six x% y% pairs walked clockwise around the outline
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>polygon hexagon</title>
<style>
body { font-family: system-ui, sans-serif; padding:24px; background:#fafafa; }
.row { display:flex; gap:32px; flex-wrap:wrap; align-items:center; }
.hex {
width:180px; height:200px; background:linear-gradient(135deg,#1976D2,#64B5F6);
color:white; display:flex; align-items:center; justify-content:center;
font-weight:700;
/* Walk clockwise from the top-lef
...3. shape-outside — Wrap Text Around a Shape
By default, text wraps around the rectangular box of a floated element, even if the element looks round. shape-outside changes the wrapping contour to a shape you choose, so paragraphs hug a circle or a pentagon like a magazine layout.
Three things must be true for it to work: the element is floated, it has an explicit width and height, and the text is a sibling after it. Use the same shape for shape-outside (the wrap) and clip-path or border-radius (the look) so they line up.
Worked example: text hugging a circle and a pentagon
float + size + shape-outside, matched with the visible shape
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>shape-outside</title>
<style>
body { font-family: system-ui, sans-serif; padding:24px; max-width:680px; margin:0 auto; }
h2 { color:#1565C0; }
.float-circle {
float: left; /* 1) MUST be floated */
width:150px; height:150px; /* 2) MUST have a size */
margin:0 20px 10px 0;
border-radius:50%; /* makes it LOOK round */
shape-outside: circle(50%); /* make
...4. Animating clip-path — Reveals & Morphs
Because clip-path is animatable, you can grow a circle from nothing to reveal an image, or morph one polygon into another. The catch: the browser can only tween between two values that use the same function with the same number of points. A circle() animates to another circle(); a 4-point polygon morphs to another 4-point polygon. Mismatched counts jump instantly instead of sliding.
Worked example: circle reveal + 4-point morph
Transition clip-path between same-shape, same-count values
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>animated clip-path</title>
<style>
body { font-family: system-ui, sans-serif; padding:24px; background:#fafafa; }
h2 { color:#1565C0; }
/* REVEAL: circle grows from 0% to 100% on hover. Same function both
sides => smooth. */
.reveal {
width:220px; height:160px; border-radius:12px;
background:linear-gradient(135deg,#F44336,#FF9800);
color:white; display:flex; align-items:center; j
...5. mask-image & Creative Sections
Where clip-path makes a hard edge, mask-image can make a soft one. A mask uses an image (often a gradient) as an alpha stencil: where the mask is opaque the element shows, where it's transparent the element fades out. A linear-gradient mask dissolves an edge into nothing — perfect for fading the bottom of a hero image.
Combine these tricks for the layouts people notice: a diagonal clip-path section edge, plus a masked fade. The example shows both.
Worked example: diagonal section + gradient mask fade
clip-path for the hard edge, mask-image for the soft one
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>mask + sections</title>
<style>
body { font-family: system-ui, sans-serif; margin:0; background:#fafafa; color:#333; }
/* Diagonal section: a polygon clip cuts the bottom edge on a slant. */
.diagonal {
background:linear-gradient(135deg,#1976D2,#42A5F5);
color:white; padding:60px 24px 90px; text-align:center;
clip-path: polygon(0 0, 100% 0, 100% 80%, 0 100%);
}
.diagonal h2 { margin
...🎯 Your Turn #1 — Clip a hexagon and an inset card
Fill in the blanks marked ___ to clip one tile into a hexagon and another into an inset rounded rectangle. Run it and check the expected result in the comments.
Your Turn #1: write a polygon() and an inset()
Complete two clip-path declarations
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Your Turn 1</title>
<style>
/* 🎯 YOUR TURN — fill in the blanks marked ___ */
body { font-family: system-ui, sans-serif; padding:24px; background:#fafafa; }
.grid { display:flex; gap:24px; flex-wrap:wrap; }
.tile { width:160px; height:160px; display:flex; align-items:center;
justify-content:center; color:white; font-weight:700; text-align:center; }
/* 1) Clip .hex into a 6-point hexagon.
...🎯 Your Turn #2 — Wrap text and add a reveal
One float needs shape-outside so the text hugs the circle, and one box needs a hover transition so its circle clip grows. Fill in the blanks, then verify against the comments.
Your Turn #2: shape-outside + animated reveal
Add a wrapping contour and a clip-path transition
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Your Turn 2</title>
<style>
/* 🎯 YOUR TURN — fill in the blanks marked ___ */
body { font-family: system-ui, sans-serif; padding:24px; max-width:640px; margin:0 auto; }
.blob {
float:left; width:150px; height:150px; margin:0 20px 10px 0;
border-radius:50%; background:linear-gradient(135deg,#9C27B0,#E040FB);
/* 1) Make the TEXT wrap around the circle, not the square box. */
___
...🧩 Mini-Challenge — A diagonal hero from scratch
Support is faded now — only an outline is given. Build a hero section with a slanted bottom edge. Lean on sections 1, 2 and 5 if you get stuck.
Mini-Challenge: diagonal hero section
A clip-path polygon edge plus a clipped shape, from a blank outline
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mini-Challenge</title>
<style>
/* 🧩 MINI-CHALLENGE: a diagonal hero
1. Style a .hero section: a background colour or gradient, white text,
generous padding, and text centred.
2. Give .hero a clip-path: polygon(...) that keeps the top flat but
slants the BOTTOM edge (hint: 0 0, 100% 0, 100% 85%, 0 100%).
3. Inside the hero, add a .badge div clipped to a circle() with a
...⚠️ Common Errors (and the fix)
- The clipped-away corners still catch clicks.
clip-pathhides pixels but keeps the full rectangular hit area, so the invisible corners still register hover and clicks (and can steal them from elements behind). Fix: addpointer-events: noneto the clipped element, or make a smaller inner element the click target. - Overflow leaks past the parent. A clipped image inside a box can still spill or show scrollbars on the wrapper. Fix: set
overflow: hiddenon the parent so anything outside the clip is contained. - shape-outside does nothing. It only applies to a floated element that has an explicit width and height. Miss the float or the size and the browser silently wraps text around the plain rectangle. Fix: add
float: left(or right) and a fixed size. - Animation jumps instead of sliding. Transitioning a 3-point shape to a 6-point shape (or
circle()topolygon()) can't interpolate. Fix: match the function and the point count on both sides — add duplicate vertices to the simpler shape if needed. - Polygon looks tangled. Listing points out of order makes edges cross. Fix: order the points by walking the outline once, clockwise or counter-clockwise — don't jump between opposite corners.
- mask works in Safari but not "everywhere". Some engines still want the prefix. Fix: write
-webkit-mask-imagealongsidemask-image, and treat the effect as progressive enhancement so unsupported browsers just see the un-masked element.
📋 Quick Reference
| Goal | Use this |
|---|---|
| Clip to a circle | clip-path: circle(50% at 50% 50%); |
| Clip to an oval | clip-path: ellipse(40% 60% at 50% 50%); |
| Inset rounded rectangle | clip-path: inset(10% round 12px); |
| Custom shape | clip-path: polygon(50% 0%, 0% 100%, 100% 100%); |
| Slant a section edge | clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%); |
| Animate a clip | transition: clip-path .5s ease; (same shape + point count) |
| Wrap text around a shape | float:left; shape-outside: circle(50%); |
| Fade an edge | mask-image: linear-gradient(black, transparent); |
| Stop clipped corners catching clicks | pointer-events: none; |
❓ Frequently Asked Questions
What is the difference between clip-path and shape-outside?
They solve two different problems and are completely independent. clip-path controls what part of an element is visible — it cuts the element to a shape, hiding everything outside that shape. shape-outside controls how other text flows around a floated element — it tells nearby text to wrap along a shape's contour instead of the element's rectangular box. clip-path changes the element's own appearance; shape-outside changes the layout of the content next to it. You often use both together (with the same polygon) so the visible shape and the text-wrap shape line up perfectly.
Why is the hidden part of my clipped element still clickable?
Because clip-path is purely visual — it only changes what you can see, not the element's geometry. The element keeps its full rectangular bounding box for layout, hit-testing, and click events, so the transparent corners you clipped away still register hover and clicks. If those invisible areas are stealing clicks from elements behind them, add pointer-events: none to the clipped element (or restructure so the clickable target is a smaller inner element).
Why won't my clip-path animation work — it just jumps instantly?
The browser can only interpolate between two clip-path values when they use the same shape function and the same number of points. circle() can animate to another circle(), and a 4-point polygon() can morph to another 4-point polygon(), but a 3-point triangle cannot smoothly become a 6-point hexagon — that produces an instant jump. The fix is to give both shapes the same point count: add invisible duplicate points (two vertices at the same coordinate) to the simpler shape so the counts match.
Why is my shape-outside being ignored — text wraps in a rectangle?
shape-outside only takes effect on a floated element that has an explicit size. Three things must all be true: the element must be floated (float: left or right), it must have a width and height (a shape needs a box to be relative to), and the wrapping text must be a sibling that comes after it in the source. Miss any one and the browser silently falls back to wrapping around the normal rectangular box. Note shape-outside changes the wrap only — pair it with clip-path or border-radius if you also want the element itself to look like that shape.
How do I read the percentages in a polygon()?
Each point in polygon() is an x y pair, written as percentages of the element's own box. The first number is horizontal: 0% is the left edge, 100% is the right edge. The second number is vertical: 0% is the top, 100% is the bottom. So polygon(50% 0%, 0% 100%, 100% 100%) means top-centre, then bottom-left, then bottom-right — a triangle. List the points in order around the outline (clockwise or counter-clockwise); jumping around produces a crossed, tangled shape.
Are clip-path, shape-outside and mask safe to use in production?
clip-path with basic shapes (circle, ellipse, inset, polygon) and mask-image are supported in every current version of Chrome, Firefox, Safari and Edge, so they are safe today. shape-outside is supported in all modern browsers too, though older Edge versions lacked it. Because these are visual enhancements, the safe pattern is progressive enhancement: build a layout that still reads fine as plain rectangles, then layer the shapes on top. If a browser ignores the property, users see a square image instead of a hexagon — not a broken page.
🎉 Lesson Complete
You can now cut, wrap and fade elements into shapes most sites never attempt. The essentials:
- ✅
circle(),ellipse(),inset()andpolygon()clip an element to a shape - ✅ Polygon points are
x% y%pairs listed in order around the outline - ✅ Animate
clip-pathonly between same-shape, same-point-count values - ✅
shape-outsidewraps text around a shape — but needs float + size - ✅
mask-imagewith a gradient fades an edge into nothing - ✅ Clipping is visual only — the full box still catches clicks unless you set
pointer-events: none
Next up: Filters & Blend Modes, where you'll blur, tint and blend layers for even richer visual effects.
Sign up for free to track which lessons you've completed and get learning reminders.