Strawberry Matcha Latte
Summer's it-girl café order, coded in three careful pours — strawberry, cream, then matcha — with bobbing ice and one very determined bubble.
← you, in 25 minutes.
what you'll build
Here's the plan
The strawberry matcha latte — summer 2026's most photographed drink — as a café sticker: a tall glass with three clean pastel layers, a coral-striped straw, ice cubes drifting at the surface, a strawberry chunk sunk in the bottom pour, and a tiny happy face on the glass. The straw's stripes are ONE dashed stroke, and the layers share wavy boundary curves so the pours look hand-poured, not ruler-drawn.
You’ll learn
- Stacking liquid layers as separate paths that SHARE their wavy boundary curves — same Bézier, reversed — so no gaps show
- Drawing a striped straw with three overlapping lines and one stroke-dasharray (zero extra shapes)
- Using paint order for free realism: the straw is drawn before the layers, so its tip sinks below the surface
- Desyncing repeated motion — two ice cubes share one bob animation but different durations, so they never move in lockstep
- Skill level
- Beginner
- Time
- ~25 min
- Tools
- Just a browser
- Code type
- SVG + CSS
- Lines
- ~76
the steps
Build it, one change at a time
Set the table
Warm paper, a soft shadow, and a sunshine coaster where the glass will land — plus a scatter of sparkles, a heart, and one tiny strawberry (foreshadowing). The shadow stays OUTSIDE the drink group so it can flatten against the table while the glass bobs.
<rect x="0" y="0" width="200" height="200" rx="18" fill="#FBF2E5" stroke="none"/> <!-- shadow first, then the coaster on top of it --> <ellipse class="sm-shadow" cx="100" cy="179" rx="48" ry="8" fill="#2B3A55" opacity="0.16" stroke="none"/> <ellipse class="sm-coaster" cx="100" cy="175" rx="43" ry="8.5" fill="#FFD84D" stroke="#2B3A55" stroke-width="3"/> <path class="sm-spark sm-spark1" d="M30 48 L34 56 L30 64 L26 56 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="2" stroke-linejoin="round"/> <path class="sm-spark sm-spark2" d="M168 58 L171 65 L168 72 L165 65 Z" fill="#FF6B5E" stroke="none"/> <!-- a tiny floating strawberry: leaf first so the body overlaps its base --> <g class="sm-berry"> <path d="M39 111 C41 106 47 106 49 111 L44 114 Z" fill="#A8C98A" stroke="#2B3A55" stroke-width="2" stroke-linejoin="round"/> <path d="M44 113 C38 112 34 117 36 123 C38 129 44 132 44 132 C44 132 50 129 52 123 C54 117 50 112 44 113 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="2.4" stroke-linejoin="round"/> <circle cx="41" cy="120" r="1.1" fill="#FFFDF7" stroke="none"/> <circle cx="47" cy="123" r="1.1" fill="#FFFDF7" stroke="none"/> </g> <path class="sm-heart" d="M160 126 C158 122 153 123 153 127 C153 132 160 135 160 135 C160 135 167 132 167 127 C167 123 162 122 160 126 Z" fill="#FF8FA3" stroke="none"/>a café table, set and waiting — coaster down, sparkles up.
Raise the empty glass
One tapered path makes the glass (wider at the rim, rounded at the base), an ellipse makes the rim, and the striped straw is THREE overlapping lines: a fat navy one for the outline, a cream one for the body, and a coral one with stroke-dasharray="6 9" — the dashes ARE the stripes. Everything lives in one sm-all group so the whole drink can bob together later.
<g class="sm-all"> <!-- tapered glass: translucent fill so the paper glows through --> <path class="sm-glass" d="M66 66 L72 166 C72.5 171 76 174 82 174 L118 174 C124 174 127.5 171 128 166 L134 66 Z" fill="#FFFDF7" opacity="0.45" stroke="#2B3A55" stroke-width="3.5" stroke-linejoin="round"/> <ellipse class="sm-rim" cx="100" cy="66" rx="34" ry="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="3"/> <!-- striped straw: 3 stacked lines, and the dashes ARE the stripes --> <g class="sm-straw" stroke-linecap="round"> <line x1="121" y1="20" x2="112" y2="84" stroke="#2B3A55" stroke-width="13"/> <line x1="121" y1="20" x2="112" y2="84" stroke="#FFFDF7" stroke-width="8"/> <line x1="121" y1="20" x2="112" y2="84" stroke="#FF6B5E" stroke-width="8" stroke-dasharray="6 9"/> </g> </g>an empty glass and a very optimistic straw.
Pour the three layers
The pours go in bottom-first — strawberry, cream, matcha — and each pair of neighbouring layers shares ONE wavy Bézier boundary (same control points, reversed) so they meet with zero gaps. Because the layers are painted AFTER the straw, its tip sinks below the matcha surface for free. Then the garnish: a strawberry chunk in the bottom pour, a bubble, two tilted ice cubes peeking at the surface, the happy face on the cream band, and a highlight streak to make the glass feel glassy.
<!-- layers painted bottom-first; each boundary curve is shared with its neighbour --> <g class="sm-layers" stroke="none"> <path class="sm-layer-berry" d="M74 136 C92 131 108 141 126 136 L124.7 165 C124.4 171 121 173 116 173 L84 173 C79 173 75.6 171 75.3 165 Z" fill="#F4A7B5"/> <path class="sm-layer-cream" d="M72.5 112 C90 106 110 118 127.5 112 L126 136 C108 141 92 131 74 136 Z" fill="#FFFDF7"/> <path class="sm-layer-matcha" d="M71 80 L129 80 L127.5 112 C110 118 90 106 72.5 112 Z" fill="#A8C98A"/> <ellipse class="sm-surface" cx="100" cy="80" rx="29" ry="4.5" fill="#BCD9A0"/> </g> <!-- a strawberry chunk sunk in the bottom pour --> <g class="sm-chunk"> <path d="M92 145 C85 144 80 150 82 156 C84 162 92 165 97 161 C101 157 101 149 97 146 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="2.5" stroke-linejoin="round"/> <circle cx="88" cy="152" r="1.2" fill="#FFFDF7" stroke="none"/> <circle cx="93" cy="157" r="1.2" fill="#FFFDF7" stroke="none"/> </g> <circle class="sm-bubble" cx="117" cy="160" r="3" fill="#FFFDF7" opacity="0.75" stroke="none"/> <!-- each ice cube gets its OWN group so it can bob on its own beat --> <g class="sm-ice sm-ice1"> <rect x="80" y="69" width="17" height="17" rx="5" fill="#FFFDF7" opacity="0.9" stroke="#2B3A55" stroke-width="3" transform="rotate(-10 88.5 77.5)"/> </g> <g class="sm-ice sm-ice2"> <rect x="100" y="73" width="13" height="13" rx="4" fill="#FFFDF7" opacity="0.9" stroke="#2B3A55" stroke-width="3" transform="rotate(12 106.5 79.5)"/> </g> <g class="sm-face"> <g class="sm-eye sm-eye-l"> <circle cx="90" cy="121" r="3.6" fill="#2B3A55" stroke="none"/> </g> <g class="sm-eye sm-eye-r"> <circle cx="110" cy="121" r="3.6" fill="#2B3A55" stroke="none"/> </g> <path d="M94 127 Q100 132 106 127" fill="none" stroke="#2B3A55" stroke-width="3" stroke-linecap="round"/> <ellipse cx="82" cy="125" rx="4.5" ry="3" fill="#FF8FA3" opacity="0.7" stroke="none"/> <ellipse cx="118" cy="125" rx="4.5" ry="3" fill="#FF8FA3" opacity="0.7" stroke="none"/> </g> <!-- one long highlight streak = instant glass --> <g class="sm-highlight" fill="none" stroke="#FFFDF7" stroke-width="4" stroke-linecap="round" opacity="0.6"> <path d="M78 92 C78.5 112 79 130 80.5 146"/> <path d="M76.5 76 L77 84"/> </g>three pours, two cubes, one chunk — officially Instagrammable.
Stir in the motion
The whole sm-all group bobs a lazy 3px while the shadow flattens beneath it. The two ice cubes share ONE bob animation but run at different durations (2.6s vs 3.4s) plus a negative delay, so they drift like real ice instead of synchronized swimmers. One bubble rides a translateY loop from the strawberry layer to the surface — fading in at the start and out at the end so the loop never pops — while the eyes blink and the props twinkle. All of it sits inside @media (prefers-reduced-motion: no-preference) so the latte holds politely still when motion isn't wanted.
@media (prefers-reduced-motion: no-preference) { .sm .sm-all { transform-origin: 100px 174px; animation: sm-bob 3.2s ease-in-out infinite; } .sm .sm-shadow { transform-box: fill-box; transform-origin: center; animation: sm-shadow 3.2s ease-in-out infinite; } /* same bob, different clocks — the cubes never sync up */ .sm .sm-ice { transform-box: fill-box; transform-origin: center; } .sm .sm-ice1 { animation: sm-ice-bob 2.6s ease-in-out infinite; } .sm .sm-ice2 { animation: sm-ice-bob 3.4s ease-in-out -1.1s infinite; } .sm .sm-bubble { transform-box: fill-box; transform-origin: center; animation: sm-bubble 4.2s ease-in infinite; } .sm .sm-eye { transform-box: fill-box; transform-origin: center; animation: sm-blink 5.2s ease-in-out infinite; } .sm .sm-spark, .sm .sm-berry, .sm .sm-heart { transform-box: fill-box; transform-origin: center; } .sm .sm-spark1 { animation: sm-twinkle 2.8s ease-in-out infinite; } .sm .sm-spark2 { animation: sm-twinkle 2.8s ease-in-out 0.9s infinite; } .sm .sm-berry { animation: sm-twinkle 3.4s ease-in-out 1.4s infinite; } .sm .sm-heart { animation: sm-twinkle 3.4s ease-in-out 2s infinite; } } @keyframes sm-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } @keyframes sm-shadow { 0%, 100% { transform: scaleX(1); opacity: 0.16; } 50% { transform: scaleX(0.92); opacity: 0.12; } } @keyframes sm-ice-bob { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-2.5px) rotate(3deg); } } /* fade in at the start, out at the end — the loop never pops */ @keyframes sm-bubble { 0% { transform: translateY(0); opacity: 0; } 12% { opacity: 0.75; } 78% { opacity: 0.75; } 92%, 100% { transform: translateY(-72px); opacity: 0; } } @keyframes sm-blink { 0%, 46%, 52%, 100% { transform: scaleY(1); } 49% { transform: scaleY(0.1); } } @keyframes sm-twinkle { 0%, 100% { opacity: 0.35; transform: scale(0.82); } 45% { opacity: 1; transform: scale(1.12); } }bobbing, drifting, blinking — café ambience, zero JavaScript.
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="sm" viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="A strawberry matcha latte gently bobbing while its ice cubes drift, a bubble rises through the layers, and the glass blinks happily">
<rect x="0" y="0" width="200" height="200" rx="18" fill="#FBF2E5" stroke="none"/>
<ellipse class="sm-shadow" cx="100" cy="179" rx="48" ry="8" fill="#2B3A55" opacity="0.16" stroke="none"/>
<ellipse class="sm-coaster" cx="100" cy="175" rx="43" ry="8.5" fill="#FFD84D" stroke="#2B3A55" stroke-width="3"/>
<path class="sm-spark sm-spark1" d="M30 48 L34 56 L30 64 L26 56 Z" fill="#FFD84D" stroke="#2B3A55" stroke-width="2" stroke-linejoin="round"/>
<path class="sm-spark sm-spark2" d="M168 58 L171 65 L168 72 L165 65 Z" fill="#FF6B5E" stroke="none"/>
<g class="sm-berry">
<path d="M39 111 C41 106 47 106 49 111 L44 114 Z" fill="#A8C98A" stroke="#2B3A55" stroke-width="2" stroke-linejoin="round"/>
<path d="M44 113 C38 112 34 117 36 123 C38 129 44 132 44 132 C44 132 50 129 52 123 C54 117 50 112 44 113 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="2.4" stroke-linejoin="round"/>
<circle cx="41" cy="120" r="1.1" fill="#FFFDF7" stroke="none"/>
<circle cx="47" cy="123" r="1.1" fill="#FFFDF7" stroke="none"/>
</g>
<path class="sm-heart" d="M160 126 C158 122 153 123 153 127 C153 132 160 135 160 135 C160 135 167 132 167 127 C167 123 162 122 160 126 Z" fill="#FF8FA3" stroke="none"/>
<g class="sm-all">
<path class="sm-glass" d="M66 66 L72 166 C72.5 171 76 174 82 174 L118 174 C124 174 127.5 171 128 166 L134 66 Z" fill="#FFFDF7" opacity="0.45" stroke="#2B3A55" stroke-width="3.5" stroke-linejoin="round"/>
<ellipse class="sm-rim" cx="100" cy="66" rx="34" ry="6" fill="#FFFDF7" stroke="#2B3A55" stroke-width="3"/>
<g class="sm-straw" stroke-linecap="round">
<line x1="121" y1="20" x2="112" y2="84" stroke="#2B3A55" stroke-width="13"/>
<line x1="121" y1="20" x2="112" y2="84" stroke="#FFFDF7" stroke-width="8"/>
<line x1="121" y1="20" x2="112" y2="84" stroke="#FF6B5E" stroke-width="8" stroke-dasharray="6 9"/>
</g>
<g class="sm-layers" stroke="none">
<path class="sm-layer-berry" d="M74 136 C92 131 108 141 126 136 L124.7 165 C124.4 171 121 173 116 173 L84 173 C79 173 75.6 171 75.3 165 Z" fill="#F4A7B5"/>
<path class="sm-layer-cream" d="M72.5 112 C90 106 110 118 127.5 112 L126 136 C108 141 92 131 74 136 Z" fill="#FFFDF7"/>
<path class="sm-layer-matcha" d="M71 80 L129 80 L127.5 112 C110 118 90 106 72.5 112 Z" fill="#A8C98A"/>
<ellipse class="sm-surface" cx="100" cy="80" rx="29" ry="4.5" fill="#BCD9A0"/>
</g>
<g class="sm-chunk">
<path d="M92 145 C85 144 80 150 82 156 C84 162 92 165 97 161 C101 157 101 149 97 146 Z" fill="#FF6B5E" stroke="#2B3A55" stroke-width="2.5" stroke-linejoin="round"/>
<circle cx="88" cy="152" r="1.2" fill="#FFFDF7" stroke="none"/>
<circle cx="93" cy="157" r="1.2" fill="#FFFDF7" stroke="none"/>
</g>
<circle class="sm-bubble" cx="117" cy="160" r="3" fill="#FFFDF7" opacity="0.75" stroke="none"/>
<g class="sm-ice sm-ice1">
<rect x="80" y="69" width="17" height="17" rx="5" fill="#FFFDF7" opacity="0.9" stroke="#2B3A55" stroke-width="3" transform="rotate(-10 88.5 77.5)"/>
</g>
<g class="sm-ice sm-ice2">
<rect x="100" y="73" width="13" height="13" rx="4" fill="#FFFDF7" opacity="0.9" stroke="#2B3A55" stroke-width="3" transform="rotate(12 106.5 79.5)"/>
</g>
<g class="sm-face">
<g class="sm-eye sm-eye-l">
<circle cx="90" cy="121" r="3.6" fill="#2B3A55" stroke="none"/>
</g>
<g class="sm-eye sm-eye-r">
<circle cx="110" cy="121" r="3.6" fill="#2B3A55" stroke="none"/>
</g>
<path d="M94 127 Q100 132 106 127" fill="none" stroke="#2B3A55" stroke-width="3" stroke-linecap="round"/>
<ellipse cx="82" cy="125" rx="4.5" ry="3" fill="#FF8FA3" opacity="0.7" stroke="none"/>
<ellipse cx="118" cy="125" rx="4.5" ry="3" fill="#FF8FA3" opacity="0.7" stroke="none"/>
</g>
<g class="sm-highlight" fill="none" stroke="#FFFDF7" stroke-width="4" stroke-linecap="round" opacity="0.6">
<path d="M78 92 C78.5 112 79 130 80.5 146"/>
<path d="M76.5 76 L77 84"/>
</g>
</g>
</svg>
tips & gotchas
Mistakes we actually made
My first layers had hairline paper-coloured gaps between them. The fix: neighbouring layers must SHARE one boundary curve — copy the Bézier and swap the two control points for the reversed direction, and the seam disappears.
I drew the straw after the layers at first and it floated on TOP of the drink like a decal. Moving it before the layers in the markup let the pours swallow its tip — paint order did the physics for me.
Giving both ice cubes the same duration with only a delay still looked robotic — they moved identically, just offset. Different DURATIONS (2.6s vs 3.4s) is what makes them drift like real ice.
The rising bubble popped out of existence at the top of every loop until I faded opacity to 0 at both ends of the keyframes. If a loop teleports, hide the teleport.
make it yours
Remix it
- Iced strawberry americanoeasy
Swap the matcha layer for a coffee brown (#8A6B4F) and the surface ellipse to match — same glass, new order.
- Extra fruiteasy
Duplicate the strawberry chunk at a new position and rotation so the bottom pour looks properly loaded.
- Mango versioneasy
Recolour the bottom pour sunshine (#FFD84D) and the chunk to a mango cube — a rounded rect does the job.
- Condensation dripmedium
Add a tiny droplet on the glass and animate it sliding down with translateY plus an opacity fade, on a slow offset loop like the bubble.
challenge extension
Give the latte a second bubble AND a gentle straw sway: add one more sm-bubble on its own delay and duration so the two never rise together, then rotate the straw ±1.5deg from a transform-origin at the rim — without letting its submerged tip escape the matcha.