Skip to content
CoDdleCoDoodle home
UI DoodlesBeginnerSVG + CSS

Squishy Button

A candy-coral button gets poked by a hand-drawn cursor, then squishes into its chunky navy base with a tiny sunshine click. It is the UI doodle equivalent of pressing the nice button just because it looks bouncy.

← you, in 25 minutes.

what you'll build

Here's the plan

A chunky coral UI button sitting on a navy base, with a cream cursor arrow that presses it like a tiny sticker interface. The finished loop keeps everything on one shared clock: the cursor glides in, the top squishes down, a sunshine burst pops, and the button springs back cleanly.

You’ll learn

  • Layering an SVG UI doodle from warm paper props to button silhouette to click details
  • Making a button feel dimensional with a base, a top face, a highlight, and one bold outline
  • Drawing a simple seven-point cursor polygon that reads like a hand-drawn pointer
  • Timing a transform-only press loop so the cursor, squish, and burst all land together
Skill level
Beginner
Time
~25 min
Tools
Just a browser
Code type
SVG + CSS
Lines
~82

the steps

Build it, one change at a time

  1. Set the click stage

    Start with the standard warm paper square, then place a few tiny props near the edges. These stay small on purpose, because the button should be the first thing your eye grabs.

    <rect x="0" y="0" width="200" height="200" rx="18" fill="#FBF2E5" stroke="none"/>
    <path class="sq-prop sq-diamond" d="M31 53 L36 62 L31 71 L26 62 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="2" stroke-linejoin="round"/>
    <circle class="sq-prop sq-dot1" cx="164" cy="50" r="3" fill="#6FA8FF" stroke="none"/>
    <circle class="sq-prop sq-dot2" cx="38" cy="142" r="2.5" fill="#5ECBB4" stroke="none"/>
    <path class="sq-prop sq-tick" d="M154 137 l9 5" fill="none" stroke="#FF8FA3" stroke-width="3" stroke-linecap="round"/>
    

    paper stage, tiny props, button drama loading.

  2. Build the squishy button

    Draw the navy base first, then stack the coral top face above it so the button already has a place to travel. The cream highlight makes the top feel glossy before any motion arrives.

    <g class="sq-button" stroke-linecap="round" stroke-linejoin="round">
      <path class="sq-btn-base" d="M45 104 C45 94 54 88 66 88 H134 C146 88 155 94 155 104 V127 C155 138 146 145 134 145 H66 C54 145 45 138 45 127 Z" fill="#2B3A55" stroke="#2B3A55" stroke-width="4"/>
      <g class="sq-btn-top">
        <path class="sq-top-fill" d="M47 76 C47 64 57 57 70 57 H130 C143 57 153 64 153 76 V105 C153 118 143 126 130 126 H70 C57 126 47 118 47 105 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="4"/>
        <path class="sq-highlight" d="M70 78 C83 68 116 68 130 77" fill="none" stroke="#FFFDF7" stroke-width="6" stroke-linecap="round" opacity="0.86"/>
      </g>
    </g>
    

    one big coral button, ready to boop.

  3. Aim the cursor

    Add a soft smile line on the button face, then draw the cursor as one clean seven-point polygon. The burst is visible here at low opacity so you can place the click moment before CSS takes over.

    <path class="sq-btn-smile" d="M76 106 C91 113 110 113 125 106" fill="none" stroke="#2B3A55" stroke-width="3" stroke-linecap="round" opacity="0.28"/>
    <g class="sq-cursor" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
      <polygon class="sq-cursor-arrow" points="130,92 165,132 146,133 155,155 143,160 134,138 120,152"/>
      <path class="sq-cursor-line" d="M145 134 L154 155" fill="none" stroke="#6FA8FF" stroke-width="2.5" opacity="0.8"/>
    </g>
    <g class="sq-burst" fill="none" stroke="#FFD84D" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" opacity="0.42">
      <path class="sq-ray sq-ray1" d="M132 54 L132 43"/>
      <path class="sq-ray sq-ray2" d="M146 61 L155 54"/>
      <path class="sq-ray sq-ray3" d="M153 77 L165 77"/>
      <path class="sq-ray sq-ray4" d="M117 61 L108 54"/>
      <circle class="sq-burst-dot" cx="158" cy="65" r="2.5" fill="#6FA8FF" stroke="none"/>
    </g>
    

    cursor lined up, little click burst waiting.

  4. Time the press

    The button top uses fill-box transforms with its origin at center bottom, so the squish compresses into the base instead of wobbling around the canvas. The cursor, button, and burst all run on the same four-second loop, and the burst starts and ends fully hidden.

    @media (prefers-reduced-motion: no-preference) {
      .sq .sq-btn-top { transform-box: fill-box; transform-origin: center bottom; animation: sq-press 4s ease-in-out infinite; }
      .sq .sq-btn-smile { transform-box: fill-box; transform-origin: center bottom; animation: sq-press 4s ease-in-out infinite; }
      .sq .sq-cursor { transform-box: fill-box; transform-origin: center; animation: sq-cursor-press 4s ease-in-out infinite; }
      .sq .sq-burst { transform-box: fill-box; transform-origin: center; animation: sq-pop 4s ease-out infinite; }
    }
    @keyframes sq-press { 0%, 34%, 68%, 100% { transform: translateY(0) scaleY(1); } 43%, 50% { transform: translateY(8px) scaleY(0.84); } }
    @keyframes sq-cursor-press { 0%, 100% { transform: translate(26px, 22px); } 32% { transform: translate(0, 0); } 43%, 50% { transform: translate(-2px, 7px); } 68% { transform: translate(11px, 11px); } }
    @keyframes sq-pop { 0%, 39%, 100% { opacity: 0; transform: scale(0.48); } 43% { opacity: 1; transform: scale(0.9); } 53% { opacity: 0; transform: scale(1.28); } }
    

    boop, squish, pop, reset with no jump.

the complete code

Everything, in one place

Skipped straight to the end? Welcome. Copy the whole thing, download it, or open it in the Playground to start remixing.

<svg class="sq" viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="A hand-drawn cursor presses a squishy coral button while a sunshine click burst pops above it">
  <rect x="0" y="0" width="200" height="200" rx="18" fill="#FBF2E5" stroke="none"/>
  <path class="sq-prop sq-diamond" d="M31 53 L36 62 L31 71 L26 62 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="2" stroke-linejoin="round"/>
  <circle class="sq-prop sq-dot1" cx="164" cy="50" r="3" fill="#6FA8FF" stroke="none"/>
  <circle class="sq-prop sq-dot2" cx="38" cy="142" r="2.5" fill="#5ECBB4" stroke="none"/>
  <path class="sq-prop sq-tick" d="M154 137 l9 5" fill="none" stroke="#FF8FA3" stroke-width="3" stroke-linecap="round"/>
  <g class="sq-button" stroke-linecap="round" stroke-linejoin="round">
    <path class="sq-btn-base" d="M45 104 C45 94 54 88 66 88 H134 C146 88 155 94 155 104 V127 C155 138 146 145 134 145 H66 C54 145 45 138 45 127 Z" fill="#2B3A55" stroke="#2B3A55" stroke-width="4"/>
    <g class="sq-btn-top">
      <path class="sq-top-fill" d="M47 76 C47 64 57 57 70 57 H130 C143 57 153 64 153 76 V105 C153 118 143 126 130 126 H70 C57 126 47 118 47 105 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="4"/>
      <path class="sq-highlight" d="M70 78 C83 68 116 68 130 77" fill="none" stroke="#FFFDF7" stroke-width="6" stroke-linecap="round" opacity="0.86"/>
    </g>
  </g>
  <path class="sq-btn-smile" d="M76 106 C91 113 110 113 125 106" fill="none" stroke="#2B3A55" stroke-width="3" stroke-linecap="round" opacity="0.28"/>
  <g class="sq-cursor" fill="#FFFDF7" stroke="#2B3A55" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
    <polygon class="sq-cursor-arrow" points="130,92 165,132 146,133 155,155 143,160 134,138 120,152"/>
    <path class="sq-cursor-line" d="M145 134 L154 155" fill="none" stroke="#6FA8FF" stroke-width="2.5" opacity="0.8"/>
  </g>
  <g class="sq-burst" fill="none" stroke="#FFD84D" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" opacity="0.42">
    <path class="sq-ray sq-ray1" d="M132 54 L132 43"/>
    <path class="sq-ray sq-ray2" d="M146 61 L155 54"/>
    <path class="sq-ray sq-ray3" d="M153 77 L165 77"/>
    <path class="sq-ray sq-ray4" d="M117 61 L108 54"/>
    <circle class="sq-burst-dot" cx="158" cy="65" r="2.5" fill="#6FA8FF" stroke="none"/>
  </g>
</svg>
Open in Playground

tips & gotchas

Mistakes we actually made

I put transform-origin at center bottom on the button top, because a squish should compress into the base instead of shrinking from the middle.

I keep the burst keyframes at opacity 0 on both ends of the loop, because a delayed hidden burst can pop in at the wrong time.

I run the cursor, button top, and burst on the same four-second clock so the click moment stays easy to tune.

I draw the cursor as a simple seven-point polygon; extra points make the arrow feel crunchy instead of hand-drawn.

make it yours

Remix it

  • Change the labeleasy

    Add a tiny cream word or icon on the button face and keep it inside the squishing top group.

  • Swap the bursteasy

    Turn the ray burst into three tiny star diamonds while keeping the same opacity window.

  • Make it double-clickmedium

    Add a second smaller press window later in the same four-second keyframes.

  • Build a control rowmedium

    Duplicate the button at smaller sizes and give each one a different accent prop or cursor angle.

challenge extension

Turn this into a tiny settings panel by adding two more SVG controls that share the same navy outline, coral accent, and transform-only interaction loop.

keep sketching

Pick another small recipe and borrow one technique for your next doodle.