Skip to main content

    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

    Clip an element to a circle, ellipse, inset rectangle or polygon
    Read and write polygon() points as x% y% coordinate pairs
    Animate clip-path for circle reveals and shape morphs
    Wrap text around a shape with shape-outside (float + size)
    Fade or knock out part of an element with mask-image
    Build diagonal sections and hexagon tiles for real layouts
    Before this lesson: you should be comfortable with CSS selectors, the box model, and percentage units, and have seen 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:

    FunctionShapeExample
    circle()Round, radius + centrecircle(50% at 50% 50%)
    ellipse()Oval, two radiiellipse(40% 60% at 50% 50%)
    inset()Rectangle pulled in from edgesinset(10% round 12px)
    polygon()Any shape from x% y% pointspolygon(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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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

    Try it Yourself »
    Code Preview
    <!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-path hides 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: add pointer-events: none to 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: hidden on 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() to polygon()) 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-image alongside mask-image, and treat the effect as progressive enhancement so unsupported browsers just see the un-masked element.

    📋 Quick Reference

    GoalUse this
    Clip to a circleclip-path: circle(50% at 50% 50%);
    Clip to an ovalclip-path: ellipse(40% 60% at 50% 50%);
    Inset rounded rectangleclip-path: inset(10% round 12px);
    Custom shapeclip-path: polygon(50% 0%, 0% 100%, 100% 100%);
    Slant a section edgeclip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
    Animate a cliptransition: clip-path .5s ease; (same shape + point count)
    Wrap text around a shapefloat:left; shape-outside: circle(50%);
    Fade an edgemask-image: linear-gradient(black, transparent);
    Stop clipped corners catching clickspointer-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() and polygon() clip an element to a shape
    • ✅ Polygon points are x% y% pairs listed in order around the outline
    • ✅ Animate clip-path only between same-shape, same-point-count values
    • shape-outside wraps text around a shape — but needs float + size
    • mask-image with 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.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service