15 CSS Chat Bubbles

15 hand-coded CSS chat bubbles for messaging apps, AI assistant interfaces, support widgets, and testimonial sections. Covers the pure-CSS bubble with a pseudo-element tail, iMessage and WhatsApp Web styles, a ChatGPT-style response bubble, animated typing indicators, image attachments, voice messages with a scrubbable waveform, and a full chat screen on flexbox. Messages use a real ordered list with <time> metadata rather than div soup. Scoped under .cb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

6 pure CSS9 light JSPublished

Related25 CSS Blockquotes25 CSS Testimonials & Reviews34 CSS Floating Buttons30 CSS Notifications22 CSS Avatars

Pure CSS Chat Bubble With Arrow — preview
01 / 15Pure CSS

Pure CSS Chat Bubble With Arrow

Six tail silhouettes drawn with zero SVG and zero extra markup, across three techniques: two ::after border-triangle variants (a baseline wedge and a centred arrow), three clip-path polygons (straight wedge, concave curl, pixel staircase), and the real curved iOS-style curl built from two pseudo-elements. Flip between them with the segmented control — the switch is a radio group read by :has(), so the whole demo is genuinely stylesheet-only, including the light/dark toggle.

Published

iMessage CSS Chat Bubbles — preview
02 / 15Light JS

iMessage CSS Chat Bubbles

An iOS-accurate message thread: true curved tails (not triangles), bubble grouping where only the last message in a run wears a tail, a blue↔green channel switch for iMessage vs SMS, long-press tapback reactions that bloom out of the bubble, and a Delivered → Read receipt that updates in the live region. The composer sends real messages.

Published

ChatGPT Response Bubble CSS — preview
03 / 15Light JS

ChatGPT Response Bubble CSS

The layout every AI product converges on: the user prompt is a bubble, the assistant answer is not. Prompts sit right-aligned in a tinted capsule; the response flows full-width as real Markdown — headings, lists, inline code, a syntax-tinted code block with a copy button — under a small model badge. Streaming is animated with a blinking block caret, there is a Stop control mid-stream, and the feedback row only appears once the answer is complete.

Published

Tailwind CSS Chat Bubble — preview
04 / 15Light JS

Tailwind CSS Chat Bubble

A chat card written the way you would actually write it in a Tailwind project — utilities on every element, dark: variants for the full colour scheme, group-hover: timestamps, peer state on the composer, and arbitrary-value utilities for the tail geometry. Ships with a scoped vanilla fallback so it renders identically here; delete that block in a real Tailwind app and the class names carry over untouched.

Published

CSS Chat Bubble Typing Indicator Animation — preview
05 / 15Pure CSS

CSS Chat Bubble Typing Indicator Animation

Four production typing indicators in one switchable demo — bounce dots, a travelling wave, a soundwave meter and a shimmer skeleton — all pure CSS, all built from the same three-span markup, all staggered by a single inline --i custom property instead of four hand-written delay rules. Each one respects prefers-reduced-motion by falling back to a non-animated resting state that still reads as "waiting".

Published

CSS Speech Bubble With Avatar — preview
06 / 15Pure CSS

CSS Speech Bubble With Avatar

Three avatar layouts for the same thread, switched with a pure-CSS radio group: beside (the classic 40 px circle next to the bubble), overlap (avatar notched into the bubble corner), and stacked (avatar and name above the message, the layout Slack-style products use). Repeated avatars inside a run are hidden but keep their space, presence is a ring rather than a dot on top of the face, and timestamps reveal on hover and focus.

Published

Live Support Widget Chat Bubble CSS — preview
07 / 15Light JS

Live Support Widget Chat Bubble CSS

A floating support launcher that expands into a real conversation card — built on the Popover API, so the browser handles the top layer, light-dismiss, Escape, and focus return with no JavaScript at all. @starting-style gives it an origin-anchored open animation, an unread badge counts waiting replies, quick-reply chips send canned questions, and the agent answers with a typing pause first.

Published

WhatsApp Web Chat Bubble CSS — preview
08 / 15Light JS

WhatsApp Web Chat Bubble CSS

The message-thread mechanics that make a mobile-messenger layout feel right: a timestamp and tick pair that floats inside the bubble and lets the last line of text wrap around it, tinted outgoing bubbles with clip-path tails, a sticky date divider, a quoted-reply block, and a receipt that walks sent → delivered → read. Send a message and watch all four states run.

Published

CSS Speech Bubble With Border — preview
09 / 15Pure CSS

CSS Speech Bubble With Border

The hardest bubble to get right: an outlined one where the tail's stroke joins the border seamlessly. Three solutions side by side — the classic two stacked triangles, a single clip-path wedge outlined with filter: drop-shadow(), and and a nested-box stroke that can carry a gradient outline. Move the tail to any of the four sides with the pure-CSS control and watch all three follow.

Published

CSS Chat Bubble Image Attachment — preview
10 / 15Light JS

CSS Chat Bubble Image Attachment

Five media bubbles that cover almost every attachment a messenger has to render: a single photo with an overlaid caption, a mosaic grid that collapses extra images into a +N tile, a video preview with duration and play affordance, a file card with a conic progress ring, and a link preview. Clicking any photo opens it in a native &lt;dialog&gt; lightbox with Escape, backdrop dismiss and focus return handled by the platform.

Published

Responsive Full Chat Screen Flexbox CSS — preview
11 / 15Light JS

Responsive Full Chat Screen Flexbox CSS

A complete two-pane messenger shell that becomes a single-pane mobile app without a media query — the breakpoint is a container query, so the same component works full-screen, in a split view or inside a 380 px sidebar. Scrollable history with a pinned composer, sticky date headers, an auto-growing textarea, unread pills, three switchable conversations and a back button that only exists in the narrow layout.

Published

CSS Comic Book Speech Bubble — preview
12 / 15Pure CSS

CSS Comic Book Speech Bubble

A four-panel comic strip with the whole balloon vocabulary drawn in CSS: a rounded speech balloon, a starburst shout cut with a 24-point clip-path, a dashed whisper, and a narration caption. Halftone dots come from a two-layer radial-gradient, panel art is duotoned with filter + mix-blend-mode, and three ink palettes swap with a pure-CSS control.

Published

CSS Voice Message Chat Bubble — preview
13 / 15Light JS

CSS Voice Message Chat Bubble

A voice-note bubble with a real scrubbable waveform — 44 bars, no canvas, no library. The played portion is revealed by a second bar layer clipped to the progress width, so the fill animates on the compositor. Click or drag anywhere on the wave to seek, use the arrow keys on the slider, switch playback speed, and open the transcript in a &lt;details&gt; that animates with ::details-content.

Published

Thought Bubble CSS Pure Code — preview
14 / 15Pure CSS

Thought Bubble CSS Pure Code

A cloud-shaped thought bubble with descending circles, built three ways: gradient cloud (the whole shape painted with layered radial-gradients), masked cloud (the same geometry used as a mask so the bubble can be filled with a photo or gradient), and outline cloud (the masked shape stroked with drop-shadow). The bubble and its trailing circles drift on independent, staggered loops.

Published

Testimonial Card Speech Bubble CSS — preview
15 / 15Light JS

Testimonial Card Speech Bubble CSS

Testimonials as speech bubbles that point down at the person who said them — semantic &lt;blockquote&gt; + &lt;cite&gt;, an accessible star rating that reads as text, and a scroll-snap carousel you can drive with the arrows, the dots, a swipe or the keyboard. Active state comes from an IntersectionObserver, so the dots stay honest even when you scroll the track by hand.

Published

FAQ

Frequently asked questions

How do I add the pointed tail to a chat bubble?
Three techniques, and the right one depends on what the bubble sits on. The border trick is the classic: a zero-size ::after with transparent borders on three sides and a coloured border on the fourth produces a triangle — border: 9px solid transparent; border-inline-end-color: var(--bubble-bg) pinned to the bubble's edge with position: absolute. It is universally supported and costs nothing, but the triangle is flat, so it cannot follow a gradient or a border. The second option is a rotated square: a small element with transform: rotate(45deg) tucked half-behind the bubble, which inherits the bubble's background including a gradient, and can carry the same border for outlined styles. The third is clip-path: polygon() on the bubble itself, cutting the tail out of one shape — elegant, but remember that clip-path also clips the element's own pseudo-elements, so any decorative extras have to move to a wrapper. Two details that separate a convincing tail from an obvious one: the tail should sit flush with the bubble's bottom edge for a chat log rather than centred, and only the last bubble in a run gets one — repeating the tail on every grouped message is what makes hand-built chat UIs look wrong.
How do I build the iMessage and WhatsApp bubble styles accurately?
Both are recognisable from three properties, and getting them slightly wrong is more noticeable than not attempting them. iMessage: outgoing bubbles use a blue gradient (roughly #0b93f6 to #0a7cff) with white text, incoming use #e9e9eb with near-black text, both at a border radius around 18px on a ~17px font. The tail is a rounded shape rather than a hard triangle, which is why a plain border triangle reads as "nearly iMessage". WhatsApp Web: outgoing is the pale green #d9fdd3 on a patterned background, incoming is white, radius is tighter at about 7.5px, and the timestamp plus delivery ticks sit inline at the bottom-right inside the bubble, floated so the text wraps around them. That inline metadata is the detail most recreations miss. In both apps, consecutive messages from the same sender group with a small gap and only the final bubble keeps its tail. Demos 02 and 08 implement both, and the copy is generic sample text rather than anything resembling either company's branding.
How do I make an animated typing indicator?
Three dots with staggered animation delays, and the whole thing is about eight lines. Each dot animates opacity or translate on a loop, offset by roughly 160ms: .dot:nth-child(2) { animation-delay: .16s } .dot:nth-child(3) { animation-delay: .32s }. Animate transform and opacity only — animating height or margin forces layout on every frame for what is a purely decorative loop. The part people skip is the accessibility. A typing indicator is a live status, so it belongs in a region with aria-live="polite" and a visually-hidden text equivalent like "Priya is typing" — three animated dots convey nothing to a screen reader. Equally important, it must stop under prefers-reduced-motion: reduce, and the honest fallback is the static text rather than three frozen dots, which look like a rendering bug. Finally, do not leave it running indefinitely: an indicator that never resolves is worse than none, so clear it on a timeout as well as on message arrival.
What is the correct semantic markup for a chat log?
A chat log is a list of messages, so it should be an <ol> — ordered, because sequence is meaningful — with each message an <li>. Inside, the sender and timestamp belong in structured markup rather than floating divs: an <article> per message with a <header> carrying the sender name and a real <time datetime="2026-08-12T14:08"> is the shape that survives both screen readers and copy-paste. Do not rely on left/right alignment alone to convey who spoke — that is visual-only information, so each message needs the sender's name in the accessible name even when it is visually hidden for grouped runs. For a live conversation, wrap the log in aria-live="polite" so new messages are announced, but scope it carefully: marking the entire scrolling history as live means every re-render re-announces everything. The usual approach is a live region containing only the newest message, with the historical log outside it.
How do I build a chat screen that scrolls correctly?
The layout is straightforward — a flex column with the header and composer fixed and the message list taking the remaining space — but two behaviours are where chat UIs usually break. First, new messages should pin to the bottom, and the modern way is flex-direction: column-reverse on the scroll container with the messages in reverse order, which makes the browser anchor scrolling to the bottom natively. Alternatively, keep normal order and use overflow-anchor, or set scrollTop = scrollHeight after append. Second, do not force-scroll when the user has deliberately scrolled up to read history — check whether they are near the bottom first, and if not, show a "new messages" affordance instead of yanking them down. On mobile, the composer needs padding-block-end: env(safe-area-inset-bottom) or the home indicator overlaps it, and the container should use 100dvh rather than 100vh so the collapsing browser chrome does not leave a gap. Demo 11 implements the full screen.
How do I display a voice message with a waveform?
The waveform is a row of bars whose heights come from real amplitude data — an array of values between 0 and 1, one per bar, written into each bar as a custom property and read by block-size: calc(8% + var(--h) * 92%). That keeps all the layout in CSS and the data in one small array. Progress is the interesting part: rather than redrawing bars as playback advances, layer a second identical waveform in the accent colour and clip it with clip-path: inset(0 calc(100% - var(--p)) 0 0), so a single custom property drives the fill. One trap this collection hit directly: do not give the bars a min-width. With 44 bars at 3px plus gaps, that is a 218px hard floor, and a chat bubble on a 320px phone has roughly 150-190px available — the row refuses to shrink and pushes the page sideways. Let the bars compress with flex: 1 1 0; min-inline-size: 0 instead. For accessibility, the waveform is decorative and should be aria-hidden; the real control is a labelled play button plus a native <input type="range"> for scrubbing.
Are these chat bubbles accessible?
The bubbles themselves are simple; the accessibility work is in the conversation around them. Colour contrast is the first risk — the pale green and light grey that messaging apps use sit close to the 4.5:1 floor, so any custom palette needs checking rather than eyeballing, and a white-on-light-blue outgoing bubble frequently fails. Sender identity must not depend on alignment or colour alone, since neither is available to a screen reader user: the name belongs in the markup even when visually hidden in grouped runs. Timestamps use <time datetime> so the machine-readable value is unambiguous regardless of how it is formatted visually. Every interactive element — play buttons, reaction triggers, expand toggles — is a real <button> with a visible :focus-visible ring at 2px minimum and a 44 by 44 CSS pixel hit target. Animated indicators respect prefers-reduced-motion and fall back to text rather than freezing. And any auto-scrolling log needs to be interruptible, since content that moves under a reader is a WCAG 2.2.2 problem as much as a usability one.
How do these chat bubbles port to React, Vue, Svelte and Tailwind?
The CSS ports unchanged; what changes is that a chat log is data-driven, so the natural shape is a <Message> component taking sender, body, timestamp and a variant, rendered from an array. Two structural notes matter more than the syntax. Message grouping — only the last bubble in a run gets a tail, and repeated senders collapse their header — is derived state, so compute it while mapping (compare each message's sender to the previous one) rather than storing a isLastInGroup flag that can drift out of sync. And auto-scroll belongs in a layout effect rather than a normal one: React's useLayoutEffect runs before paint, so the jump to the bottom happens without a visible flash, whereas useEffect shows one frame at the old position. Vue's nextTick and Svelte's afterUpdate serve the same purpose. In the Next.js App Router the log itself can render on the server while only the composer and live updates need 'use client'. For Tailwind, demo 04 is already written as utilities, with the tail kept as a small piece of real CSS since a pseudo-element triangle has no clean utility equivalent.

Related collections

30 CSS Badges preview

30 CSS Badges

30 hand-coded CSS badges for status indicators, notifications, membership tiers, live-data displays, and SEO / DevOps / financial dashboards — upload progress, typing indicator, transit line status, Core Web Vitals, ECG heartbeat, CI/CD build pipeline, countdown ring, live price ticker, keycap shortcut, wax seal, conference lanyard, and holographic collectibles. Copy-paste HTML and CSS, WCAG 2.2 accessible, MIT licensed.

20 CSS Banners & Alerts preview

20 CSS Banners & Alerts

20 hand-coded CSS banner and alert bars for e-commerce storefronts, SaaS dashboards, marketing sites, and compliance-bound products. Covers GDPR cookie consent with a real preferences panel, promo bars with countdown timers, free-shipping threshold progress, app-install smart banners, maintenance and incident status bars, newsletter signup bars, age verification gates, limited-stock urgency banners, live event announcements, browser upgrade notices, geo and currency switchers, sticky top announcement bars, bottom cookie bars, inline form validation alerts, icon-aligned alert banners, left-border accent alerts, diagonal stripe promos, full-width hero banners, animated gradient border alerts, and a text-wrapping laboratory that shows five strategies for the unbreakable string that breaks more banners than anything else. Every bar is scoped under a .ba-NN prefix for no-collision pasting, guards prefers-reduced-motion, carries the correct aria-live and role semantics, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.

25 CSS Blockquotes preview

25 CSS Blockquotes

25 hand-coded CSS blockquote designs for SaaS testimonials, editorial pull quotes, documentation callouts, and developer engineering blogs: large decorative quotation marks with ::before / ::after glyphs, Tailwind CSS blockquote component, pure CSS callout / admonition boxes for Docusaurus / Mintlify / Nextra documentation, responsive center-aligned quote banners, modern minimalist left-border editorial style, animated CSS gradient border, classic left border, brutalist high-contrast, glassmorphism frosted card, glowing neon border, article pull quote with text-wrap, speech bubble chat-style, thick vertical accent rail, customer testimonial cards with avatar and star rating, inline highlighted text, marker highlight underline, floating drop-shadow, aurora gradient background, Twitter / X card style, multi-column newspaper editorial, IDE code-comment style, Markdown-compatible defaults, JavaScript testimonial quote slider, expandable read-more, and one-click copy button. 22 Pure CSS + 3 Light JS (slider, expand, copy). Every design scoped under .bq-NN prefix for no-collision pasting, prefers-reduced-motion guarded per WCAG 2.3.3, WCAG 2.2 AA accessible, framework-agnostic (React, Vue, Svelte, Astro, Next.js, Nuxt, Remix, SvelteKit).

Search CodeFronts

Loading…