Add a box-shadow to a bubble that uses clip-path and watch it vanish. MDN documents that clip-path hides every part of an element outside its clipping region, so the shadow has to move to a drop-shadow filter on a parent element.
Little surprises like that are why the tail method matters more than the bubble itself. The bubble is a rounded text box with a pointer, drawn from CSS shapes alone, with no image or SVG file, and it grows with its text. You’ll find it in chat interfaces and testimonial cards. The tail can come from a border triangle, a rotated square, a clip-path polygon or a mask with gradients, and each choice changes what else keeps working.
What is a CSS speech bubble made of?
See the Pen
pure css speech bubble by ChonnyChu (@chonny)
on CodePen.
Mostly a rounded box and a pointer. The box is a p or div with a background, padding and border-radius. The pointer, called the tail, is a ::before or ::after pseudo-element positioned absolutely against it. The box also needs position: relative, otherwise the tail’s offsets get measured from some ancestor instead of the bubble’s own edges.
Nicolas Gallagher’s 2010 tutorial on pure CSS speech bubbles already kept the markup to one element and pushed every visual detail into the stylesheet. That approach still holds.
Border-radius sets the curve (CSS Backgrounds and Borders Module Level 3 defines it), and padding keeps text off the corners. Add a max-width in ch so the bubble doesn’t stretch across the whole page.
Start with one radius on all four corners. A border radius generator helps once the corners need to differ, which happens as soon as a tail sits near one of them.
The tail needs content: "". Without it the pseudo-element is not generated at all.
.bubble {
position: relative;
max-width: 28ch;
padding: 0.75em 1em;
border-radius: 12px;
background: #e8f0fe;
}
.bubble::after {
content: "";
position: absolute;
}Put together, the build goes in this order.
- Write the body with background, padding, border-radius and a max-width in ch.
- Set position: relative on the body.
- Add ::after with an empty content string and position: absolute.
- Give the tail its shape (border triangle, clip-path or mask) and offset it with top, bottom, left or right.
How does the border triangle method build a tail?
See the Pen
Speech Bubble Caret by GRAY GHOST (@grayghostvisuals)
on CodePen.
A zero-size element with one colored border and three transparent ones renders as a triangle, and that triangle is the tail. It needs only border, position and content properties.
The colored border is the one facing the bubble. So border-top-color gives a tail that hangs below the bubble and points down.
.bubble::after {
content: "";
position: absolute;
top: 100%;
left: 24px;
border: 10px solid transparent;
border-top-color: #e8f0fe;
}The other directions follow the same logic, each with an offset on the free axis:
- Tail above, pointing up: bottom: 100% and border-bottom-color
- Tail on the left, pointing left: right: 100% and border-right-color
- Tail on the right, pointing right: left: 100% and border-left-color
Code Boxx’s responsive bubble tutorial runs this trick through four modifier classes (up, down, left, right). A CSS triangle generator produces the border values for any size, which saves the trial and error.
The appeal is how little it takes. One pseudo-element, a handful of properties that matter, and no support checks in current browsers, since it relies on basic border and positioning rules. The tail size changes with a single border-width value, in px or em.
The downside is the tip. It’s sharp, the edges are straight, and the triangle can’t follow border-radius, so a tail placed at a rounded corner looks detached. An outline needs a second triangle (covered below).
How to build a tail with clip-path or mask
See the Pen
Clip-path speech bubble w/ hover effects by Michael Robinson (@mrbnsn)
on CodePen.
Clip-path and mask draw the tail on the same element as the body, so the basic shape needs no pseudo-element. Clip-path suits straight-edged tails, and mask suits rounded ones.
Using clip-path and border-image fill
Temani Afif’s two-property tooltip, published in Smashing Magazine in March 2024, sets the color with border-image and the fill keyword. The border-image outset paints an overflowing color past the box, covering the area where the tail goes, and clip-path with polygon() cuts out the rectangle plus the pointer.
The same series moves the tail with a –p custom property and reshapes it with –x. A clip-path generator is quicker for sketching the polygon points before converting them to percentages.
Everything lives on one element, with no pseudo-element, and tail size and position sit in custom properties. It has been available across browsers since January 2020 (MDN).
The catch is that clip-path clips everything outside the shape, box-shadow included, and it creates a new stacking context (MDN). The border-radius also needs adjusting to the tail position (Smashing Magazine, 2024).
Using mask and conic-gradient for rounded corners
Rounded corners are where clip-path polygons stop being practical, and the mask method takes over. In Afif’s version for Verpex, the tail is set by a height and an angle (a 20px tail at 60deg in the demo), where the clip-path version uses a width and a height.
His CSS-Tip bubble builds the rounded body from stacked radial and conic gradients, driven by two variables: –r for the radius and –t for the tail size.
One rule gives a rounded body and a rounded tail, and the tail angle is adjustable. The stacked gradients are hard to read, though, and published snippets still pair mask with -webkit-mask.
The unprefixed mask shorthand has been available across browsers since December 2023, and conic-gradient since November 2020 (both per MDN). A mask generator saves the manual gradient math.
Resizable tails with the shape() function
The older path() function takes SVG path data in fixed CSS pixels, so a bubble drawn with it does not grow with its text. MDN’s example shows the failure: on a larger element the shape stays the same size, and the top and left borders end up partly visible while the right and bottom borders are clipped away.
The shape() function accepts percentages, calc() and custom properties, so the clipped shape scales with the element. It became Baseline Newly Available in February 2026, when Firefox 148 shipped.
| Browser | First version with shape() | Release date |
|---|---|---|
| Safari | 18.4 | 31 March 2025 |
| Chrome | 135 | 1 April 2025 |
| Firefox | 148 | 24 February 2026 |
Source: Web Platform DX web-features data, as of October 2026.
Browsers older than those versions drop the declaration, so write a polygon() clip-path first and the shape() rule after it.
How to add an outline and shadow to a bubble with a tail
See the Pen
CSS Speech Bubble Outline (svg data uri) by Florian Geierstanger (@fgeierst)
on CodePen.
A border on the bubble body stops at the body, and neither tail method carries it around the tail. Both get fixed the same way, by drawing a slightly smaller layer on top of a first layer in the border color.
On border triangle bubbles that means stacking two triangles. The back one is 1px larger and carries the border color, and the front one carries the background color and sits 1px to the side (30secondsofcode’s example uses an 11px triangle behind a 10px one).
Clip-path bubbles get a pseudo-element inset by the border thickness, with clip-path: inherit so it reuses the body’s shape. Part 2 of the Smashing Magazine series (March 2024) reports that the tail’s border comes out thinner than the body’s until the tail is defined by an angle and a height instead of a base and a height.
Shadows split the two methods further. Box-shadow works on the body of a border triangle bubble, and a box shadow generator speeds up the tuning, but the tail gets no shadow from it.
Clip-path and mask-image clip a box-shadow along with the element, so any shadow outside the shape disappears (CSS-IRL). The fix is filter: drop-shadow() on a parent wrapper around the clipped element. On a border triangle bubble, drop-shadow on the bubble itself covers body and tail together.
Drop-shadow follows the alpha shape, so the tail casts a shadow too. CSS-IRL also notes that box-shadow renders darker and heavier than drop-shadow at the same values, so lower the alpha when switching.
If the two layers drift apart, untick the clip-path declaration in Chrome DevTools. The unclipped boxes show exactly where the misalignment starts.
Which tail method should you pick?
Use the border triangle for flat-color bubbles with square corners, clip-path for single-element components with a variable-driven tail, and mask only when the body and tail both need rounding. The rotated square sits between them: a small square turned 45 degrees behind the body, which gives the tail its own shadow.
| Method | Rounded tail | Shadow route | Setup cost |
|---|---|---|---|
| Border triangle | No | drop-shadow on the bubble | Low |
| Rotated square | Optional, via border-radius | Its own box-shadow | Low |
| clip-path polygon() | No | drop-shadow on a wrapper | Medium |
| mask with gradients | Yes | drop-shadow on a wrapper | High |
Skip mask when a border triangle will do. A notification bubble with a flat color and straight corners gains nothing from stacked gradients, and the extra rule is one more thing to debug.
Browser support and failure conditions
The border triangle only works with a flat color. Give the body a gradient and you get a visible color jump at the tail, because the triangle cannot continue the gradient.
The rotated square has half of itself sitting inside the body, so it needs the same background and a z-index behind the text. H5P’s JoubelUI stylesheet does exactly this with a 12px square rotated 45deg, a box-shadow of its own and z-index: -1.
Clip-path means a wrapper if you want the shadow, so the component carries two nodes in the DOM.
Mask hides the shape inside gradient stops, so a misaligned tail means reading layered gradients. FreeFrontend’s listing of a single-element version highlights a gradient that flows across both body and tail, which a triangle cannot do.
Support rarely decides the choice. Every method above sits at Widely available status in Baseline except shape(), which keeps its polygon() fallback, and Can I use gives the exact version floor before a fallback is dropped.
How to position the tail and align chat bubbles
Pick the side with top, bottom, left or right, set the offset with one value or variable, and mirror the rule for the opposite side. Chat lists add one step: the tail side flips between sent and received messages.
Setting the tail side and offset
See the Pen
Alternating speech bubbles by Kevin Østerkilde (@Kosai106)
on CodePen.
A fixed offset like left: 24px holds the tail at a constant distance from the corner. Use left: 20% and it follows the bubble as its width changes. Centering takes left: 50% with transform: translateX(-50%), and if you want the whole thing variable-driven, a –p custom property can be read by left or by the clip-path polygon.
Offsets near the end of an edge fail first. In Temani Afif’s Smashing Magazine tutorial, pushing the position variable to its extremes slides the tail off the end of the bubble.
Clamp the offset instead. Keep it at least one corner radius plus half the tail base away from either end, so the tail stays on the straight part of the edge.
Sent and received messages in a chat list
See the Pen
Apple iMessage in CSS by Matt Smith (@AllThingsSmitty)
on CodePen.
Bottom-aligning a message list with justify-content: flex-end makes the earliest messages unreachable once the list overflows, according to Chen Hui Jing’s box alignment write-up. Her fix is margin-top: auto on the first child, which keeps the list pinned to the bottom and still scrollable.
Sent bubbles take align-self: flex-end, and received ones keep the default start alignment. The tail moves with them, right side for sent and left side for received.
- Flex alignment follows dir=”rtl” without extra rules, while margin-based alignment needs manual overrides (Alex Chan, 2026)
- Facebook Messenger varies corner rounding across consecutive bubbles, with three states that differ for sender and recipient (Ahmad Shadeed’s component breakdown)
- Showing the tail only on the last bubble of a run keeps a long thread from looking spiky
An iMessage-style corner tail has a catch. freeCodeCamp’s build overlaps two elements at the bubble corner and colors one to match the chat screen, so the cut-out only works on a solid chat background.
Bubbles are one piece of a CSS chat box, which also needs a scrolling message list, a header and an input row.
How to make a speech bubble responsive
See the Pen
responsive speech bubble by peros (@perossing)
on CodePen.
Size the body in ch or % so the text wraps, size the tail in em so it scales with the font, and offset the tail in % so it holds its place as the width changes.
A max-width in ch counts characters, so line length stays readable at any font size. Padding derived from the radius keeps text clear of the curve (css-tip’s bubble uses calc(2*var(–r)/3)). And if the bubble width isn’t fixed, use % for the tail offset, not px.
Pick em when the tail should follow the bubble’s own text size, and rem when it should follow the root. The em versus rem trade-off is the same one that applies everywhere else in a stylesheet.
Container queries became Baseline Widely available in August 2025 (web.dev), after landing in all major engines in 2023.
A bubble in a narrow chat column and the same bubble in a wide card can switch tail side or font size from the container’s width. Media queries react to the screen instead, which is the wrong signal for a component dropped into different slots.
Container units go further. One cqi equals 1% of the container’s inline size, so the tail size can follow the container directly.
How to keep a speech bubble accessible
Most of the accessibility work sits in the text, not the tail. The tail needs no treatment while its generated content stays an empty string.
Test the text against the bubble background, not the page behind it. WCAG 2.2 Success Criterion 1.4.3 sets the thresholds, and the W3C’s Understanding document says computed ratios are not rounded, so 4.499:1 fails. Normal text needs at least 4.5:1. Large-scale text gets by with 3:1, which means 18pt or 14pt bold (about 24px and 18.5px).
A pastel bubble with white text is the usual offender. Run the pair through a color contrast check before the shape work starts.
Pick the element to match the content. A single message fits a p. Quotes and testimonials belong in a blockquote (Nicolas Gallagher’s demo styles blockquote elements as bubbles), and a message list is a ul or ol with li items.
For a live chat list, put role=”log” on the container. MDN names chat logs as the example, and the role announces new messages when the user is idle. It carries an implicit aria-live of polite, and an accessible name is required, so name the log with aria-label or aria-labelledby. See the ARIA role basics if the attribute set is new.
Generated content is exposed to assistive tools, per MDN’s breadcrumb recipe. An empty string has nothing to read, so the tail stays silent.
If the tail uses an image, add an empty alternative: content: url(tail.svg) / "";. And do not let bubble color be the only sign of who sent a message, since a visible name or a visually hidden label covers readers who cannot tell the colors apart.
CSS Speech Bubble Examples To Check Out
Speech Bubble by skoupidia12000
See the Pen
speech bubble by Panos (@skoupidia12000)
on CodePen.
Clean CSS Speech Bubble by projectxmatt
See the Pen
Clean CSS Speech Bubble. by projectxmatt (@projectxmatt)
on CodePen.
Pure CSS Speech Bubble with a Shadow by Ron
See the Pen
Pure CSS Speech Bubble with a Shadow by Ron (@keith0305)
on CodePen.
Speech Bubble Testimonial by Symbolic
See the Pen
Speech bubble testimonial by Symbolic (@symbolicx)
on CodePen.
Animated Speech Bubble Nav
See the Pen
Animated Speech Bubble Nav by schadeck (@schadeck)
on CodePen.
Speech Bubble Wow by bchiang7
See the Pen
Speech Bubble wow by Brittany Chiang (@bchiang7)
on CodePen.
Circular Speech Bubbles
See the Pen
circular speech bubbles by rajeshdn (@RajRajeshDn)
on CodePen.
Speech Bubbles 3 by cool_lazyboy
See the Pen
speech bubbles 3 by rajeshdn (@RajRajeshDn)
on CodePen.
Speech Bubble Slider by Marc
See the Pen
Speech Bubble Slider by Marc (@mburgess0899)
on CodePen.
Shakespearean Insult Generator by Kris Bocz
See the Pen
Shakespearean Insult Generator – CodePen challenge by Kris Bocz (@kbocz)
on CodePen.
Flat Responsive Speech Bubbles by Faunk
See the Pen
Flat Responsive Speech Bubbles by Fabian Fink (@Faunk)
on CodePen.
Chat Bubbles by Dave Alger
See the Pen
chat bubbles by Dave Alger (@run-time)
on CodePen.
CSS Speech Bubble by Rm.satrya
See the Pen
CSS Speech Bubble by Ga Satrya 🇮🇩 (@satrya)
on CodePen.
Speech Bubble W/ Drop Shadow by Syahrasi
See the Pen
CSS speech bubble w/ dropshadow by Syahrasi (@syahrasi)
on CodePen.
Rik Schennink’s Speech Bubble
See the Pen
Speech bubble by Rik Schennink (@rikschennink)
on CodePen.
Skew’d Bubble Dink by Jase
See the Pen
Skew’d Bubble Dink (CSS) by Jase (@jasesmith)
on CodePen.
SVG Speech Bubble by Timothy Miller
See the Pen
SVG speech bubble by Timothy Miller (@webinspect)
on CodePen.
Comic Book Speech Bubbles with Dudley Storey
See the Pen
Comic Book Speech Bubbles with SVG by Dudley Storey (@dudleystorey)
on CodePen.
Hello, World by kirsten allen
See the Pen
Hello, World by kirsten allen (@kirstenallen)
on CodePen.
Black Lives Matter Dialog Boxes by Rio Jos
See the Pen
Black Lives Matter Dialog Boxes by Rio Jos (@riojosdev)
on CodePen.
CSS Stacked Chat Bubbles by J.M. CIery
See the Pen
CSS stacked chat bubbles (Messenger style) by J.M. CIery (@jmpp)
on CodePen.
Dustin Dowell’s Pure CSS iOS Chat Bubbles Sass Mixin
See the Pen
Pure CSS iOS Chat Bubbles Sass Mixin by Dustin Dowell (@dustindowell)
on CodePen.
Ana Tudor’s Speech Bubble
See the Pen
Speech bubble by Ana Tudor (@thebabydino)
on CodePen.
Pure CSS WhatsApp Desktop Speech Bubble by Benni
See the Pen
Pure CSS WhatsApp Desktop Speech Bubble by Benni (@8eni)
on CodePen.
CSS Chat Bubbles by Andrew
See the Pen
CSS Chat Bubbles by Andrew (@andrewerrico)
on CodePen.
Sassy Right Triangles? Katherine G Shaw’s Speech Bubble Blockquote
See the Pen
Speech Bubble Blockquote with Sassy Right Triangles by Katherine G Shaw (@KatherineGShaw)
on CodePen.
Just the Speech Bubble by Fivera
See the Pen
Speech bubble by Fivera (@fivera)
on CodePen.
Jamesbarnett’s Take on CSS Speech Bubble
See the Pen
CSS speech bubble by James Barnett (@jamesbarnett)
on CodePen.
Convo Boxes Galore with Carles Codony
See the Pen
speech bubbles by Carles Codony (@bitblitter)
on CodePen.
Depthdev’s Callouts & Speech Bubbles
See the Pen
CSS Callouts/Speech Bubbles by Adam (@depthdev)
on CodePen.
Chat Bubbles on an iPhone by Stephen Zuniga
See the Pen
Chat bubbles in iPhone by Stephen Zuniga (@stezu)
on CodePen.
Grant’s Pure CSS Thought Bubbles
See the Pen
Pure CSS Thought Bubbles by Grant (@quadbaup)
on CodePen.
Patrick Kleckner’s 8-Bit Balloon
See the Pen
8-Bit by Patrick Kleckner (@pkleckner)
on CodePen.
Argyleink’s Animated Comic Director Splash
See the Pen
Comic Director Splash Animation by Adam Argyle (@argyleink)
on CodePen.
CSS Speech Bubble FAQ
Is a speech bubble the same thing as a tooltip?
No, but they share one design idea: a text box with a pointing tail, according to Temani Afif’s tutorial for Verpex. A tooltip adds behavior, appearing on hover or focus and anchoring to a trigger element.
How do you turn a speech bubble into a thought bubble?
See the Pen
Pure CSS speech and thought bubbles by Joe Hastings (@JoeHastings)
on CodePen.
Replace the pointed tail with a trail of shrinking circles. FreeFrontend’s Pure CSS Thought Bubbles example builds the trail from shadows that inherit the background color, so the circles always match the body.
Is a generator faster than writing the CSS by hand?
Yes, for a known shape. Afif’s CSS Generators site lists 100 tooltip and speech bubble variations, each built from one element and each with copy-ready CSS.
Hand-written CSS pays off once the bubble needs behavior a preset lacks.
Does Bootstrap or Tailwind CSS include a speech bubble component?
No. Bootstrap ships tooltips and popovers with an arrow, positioned by Popper, which is a hover pattern rather than a static bubble. Tailwind CSS has no bubble component either, but its after: variant reproduces the pseudo-element tail.
Can a speech bubble animate in without causing layout shift?
See the Pen
Animated Speech Bubble (Bouncy) by Khaled Ahmed Younes (@KhaledAhmedYounes)
on CodePen.
Yes, when the animation changes only transform and opacity. web.dev’s animation guidance says to stick to those two properties, because animating layout properties forces reflow.
CSS keyframes handle the timing, and a transform-origin at the tail tip makes the bubble grow out of the speaker.
Can the same bubble work as a popover?
Yes. The HTML popover attribute, Baseline Newly available since January 2025 (web-features data), renders the bubble in the top layer, so a parent’s overflow or position rules cannot clip it.
MDN’s examples pair it with anchor positioning to place the element beside its trigger. For hover-only cases, a plain CSS tooltip pattern is enough.
When an Inline SVG Bubble Beats CSS
A CSS speech bubble stops being the right build when the tail must curve along an arbitrary outline. An inline SVG bubble draws any curve exactly.
The switch has a cost: the W3C’s SVG 1.1 specification states that SVG performs no automatic line breaking or word wrapping. Stuff & Nonsense placed a foreignObject paragraph inside each of the eight speech bubbles on its about page to get text that wraps.
The trade-off is exact curves in exchange for owning the text flow, which HTML handles for free.
The shape() function draws curves with percentage units and is expected to reach Baseline Widely available on 24 August 2028 (web-features data, checked October 2026). From that date a curved tail can stay in CSS without a polygon() fallback.
Until then, embedding SVG in HTML covers the cases CSS cannot.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026


