webdevpuneet.com

WhatsApp Floating Button — Free HTML CSS JS Contact Widget Snippet

WhatsApp Floating Button · Buttons · Plain HTML, CSS & JS · Live preview

CategoryButtons

Copy & paste

Source Code

<div class="page-demo">
  <h1>Contact page</h1>
  <p>A floating WhatsApp button stays fixed in the corner of the viewport, with a pulsing ring, an unread badge and a greeting bubble that shows whether your team is online.</p>
  <div class="wa-controls">
    <button type="button" id="ctlOnline">Online</button>
    <button type="button" id="ctlSide">Right</button>
    <button type="button" id="ctlReplay">Replay greeting</button>
  </div>
</div>

<div class="wa-widget" id="waWidget" data-position="right">
  <div class="wa-bubble" id="waBubble" role="dialog" aria-label="Chat with us on WhatsApp" hidden>
    <button class="wa-close" id="waClose" type="button" aria-label="Dismiss chat prompt">&times;</button>
    <div class="wa-head">
      <span class="wa-avatar" aria-hidden="true">S</span>
      <div class="wa-who">
        <strong>Support team</strong>
        <span class="wa-status"><i class="wa-dot" id="waDot"></i><span id="waStatus">Online now</span></span>
      </div>
    </div>
    <p class="wa-msg" id="waMsg">Hi 👋 Have a question? Chat with us on WhatsApp.</p>
  </div>

  <a class="whatsapp-fab" id="waFab" href="https://wa.me/15551234567?text=Hi!%20I%20have%20a%20question" target="_blank" rel="noopener noreferrer" aria-label="Chat with us on WhatsApp">
    <span class="pulse-ring"></span>
    <svg viewBox="0 0 32 32" width="28" height="28" fill="#fff" aria-hidden="true">
      <path d="M16.004 3C9.373 3 4 8.373 4 15.004c0 2.65.86 5.1 2.32 7.09L4.5 28.5l6.58-1.79a11.9 11.9 0 0 0 4.92 1.07h.005c6.63 0 12.004-5.373 12.004-12.004S22.634 3 16.004 3zm0 21.8h-.004a9.78 9.78 0 0 1-4.98-1.36l-.357-.212-3.68 1 .984-3.586-.233-.368a9.77 9.77 0 0 1-1.5-5.27c0-5.4 4.396-9.8 9.8-9.8 2.617 0 5.077 1.02 6.93 2.87a9.73 9.73 0 0 1 2.868 6.93c0 5.4-4.397 9.796-9.828 9.796zm5.36-7.34c-.293-.147-1.735-.856-2.004-.953-.27-.098-.466-.147-.663.147-.196.293-.76.953-.932 1.15-.172.196-.343.22-.636.073-.293-.147-1.238-.456-2.358-1.454-.872-.777-1.46-1.737-1.632-2.03-.172-.293-.018-.452.13-.598.133-.132.293-.343.44-.514.146-.172.195-.294.293-.49.098-.196.049-.368-.024-.514-.073-.147-.663-1.6-.91-2.19-.24-.575-.482-.497-.663-.507l-.564-.01c-.196 0-.514.073-.783.368-.27.294-1.03 1.007-1.03 2.456 0 1.45 1.055 2.85 1.202 3.046.147.196 2.076 3.17 5.03 4.443.703.303 1.25.484 1.678.62.705.224 1.347.192 1.855.117.566-.084 1.735-.71 1.98-1.395.245-.686.245-1.273.172-1.395-.073-.123-.27-.196-.563-.343z"/>
    </svg>
    <span class="wa-badge" id="waBadge" aria-hidden="true">1</span>
  </a>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Uses WhatsApp's official wa.me deep link format: no API key or SDK integration required
Pre-filled message is built with encodeURIComponent and can include the current page title
Business-hours check in a fixed time zone drives an online or offline state
Greeting bubble with avatar and status dot, dismissible by button or Escape key, remembered per session
Unread badge that clears once the bubble opens or the button is clicked
Pulse ring is a separate element, so the button itself never resizes; it stops when offline
Left or right placement via one data-position attribute
Safe-area inset keeps the button clear of the iPhone home indicator
prefers-reduced-motion disables the pulse and bubble animations
Analytics hook pushes a whatsapp_fab_click event to dataLayer
Real anchor with aria-label, focus-visible outline and rel="noopener noreferrer"
No framework, no chat widget library, no build step

About this UI Snippet

WhatsApp Floating Button — Contact Widget With Greeting Bubble and Business Hours

Screenshot of the WhatsApp Floating Button snippet rendered live

A floating WhatsApp button is one of the simplest, highest-impact additions to a small-business or support-facing website: a single fixed circular button that opens a pre-filled WhatsApp chat, no phone app plugin or chat SDK required. This version goes further than a bare icon: it adds an unread badge, a dismissible greeting bubble, and an online or offline status driven by your business hours.

How the WhatsApp link works

The button is a plain <a> tag pointing at a wa.me link: https://wa.me/15551234567?text=.... wa.me is WhatsApp's own official link format and needs no API key or app integration. The number after wa.me/ is the full international phone number with no +, spaces, or leading zeros. The ?text= parameter pre-fills the message box; the script builds it with encodeURIComponent, so spaces and special characters are always encoded correctly, and it swaps {page} for the current page title so your team knows which page the visitor was on.

Business hours and online status

isOnline() reads the current weekday and hour in a fixed time zone (CONFIG.hours.timeZone) using Intl.DateTimeFormat, so "9 to 6, Monday to Friday" means *your* team's hours, not the visitor's clock. When it's outside those hours, the status dot turns grey, the pulse ring stops, the unread badge hides, and the bubble asks visitors to leave a message. The pre-filled text also gets "(sent outside business hours)" appended, so whoever picks it up knows why the reply took a while.

The greeting bubble

After showAfterMs the bubble opens above the button with an avatar, the status line and a short message. The close button and the Escape key both dismiss it, and the dismissal is remembered in sessionStorage so it doesn't nag on every page of the same visit (wrapped in try/catch, so a blocked storage never breaks the widget).

How the pulsing ring works

Behind the button sits a second circle, .pulse-ring, sized identically via inset: 0 with z-index: -1. The button has its own stacking context, so the ring always renders behind the solid button. A @keyframes pulse animation scales it from 1 to 1.9 while fading from 0.6 to 0, so only the ring visibly pings outward and the button never changes size.

Left or right, and phone-friendly

The fixed wrapper carries data-position="right|left", and the bubble tail flips with it. bottom includes env(safe-area-inset-bottom) so the button never sits under the iPhone home indicator.

Respecting reduced motion

A prefers-reduced-motion: reduce query disables the pulse and the bubble animation for users who are sensitive to motion at the OS level. The widget stays fully functional.

Step by step

How to Use

  1. 1
    Load the snippetClick "WhatsApp Floating Button" in the sidebar Library tab. The preview shows the pulsing green button with an unread badge, then the greeting bubble opens after a couple of seconds.
  2. 2
    Set your phone numberIn the JS panel, change CONFIG.phone to your business number in international format: digits only, no + or leading zeros.
  3. 3
    Customize the messageEdit CONFIG.message. Use {page} to insert the current page title. The script URL-encodes the text for you.
  4. 4
    Set your business hoursEdit CONFIG.hours (time zone, days and from/to hours). Outside those hours the widget shows as offline.
  5. 5
    Try the demo controlsUse the Online/Offline, Left/Right and Replay greeting buttons to preview each state, then delete those buttons and the "Demo-only controls" code at the bottom of the JS.
  6. 6
    Adjust position and animationSwitch data-position between right and left, change bottom/right offsets in .wa-widget, or tune the pulse keyframes' scale and opacity.
  7. 7
    Export in your formatClick "HTML" for a standalone file, "JSX" for React, or "Tailwind" for React + Tailwind CSS.

Real-world uses

Common Use Cases

CHAT
Small business and support contact
Give website visitors a one-tap way to start a WhatsApp conversation with your business, without needing a full chat widget SDK.
Set expectations with business hours
Show visitors honestly when your team is away, and tag out-of-hours messages so your team can prioritise replies.
Pre-fill context per page
Use the {page} placeholder, or build the text from a product name, so every conversation starts with the context your team needs.
Learn CSS-only pulse/ping animations
Study how a second absolutely-positioned element with its own keyframe animation creates a radiating ring independent of the parent button's size.
Match your brand's floating action style
Adjust the button color, size, bubble and pulse ring to match other floating elements already on your site.
Respect motion sensitivity preferences
The prefers-reduced-motion query here is a good reference pattern for any other decorative CSS animation on your site.

Got questions?

Frequently Asked Questions

No. This uses wa.me, WhatsApp's free official click-to-chat link format that works with any regular or Business WhatsApp account. No API key, approval process, or paid integration is required.

Use the full number in international format with the country code, but with no plus sign, spaces, dashes, or leading zeros. For example 15551234567 for a US number starting with +1.

Edit CONFIG.message in the JavaScript. The script URL-encodes it with encodeURIComponent and appends it as ?text= on the wa.me link, so spaces and special characters are handled for you. Use {page} to insert the current page title.

isOnline() checks the current weekday and hour in the time zone set in CONFIG.hours using Intl.DateTimeFormat, then compares them with your days and from/to hours. It uses your team's time zone, not the visitor's clock. Outside those hours the dot turns grey, the pulse stops and the bubble asks for a message.

No. It only reflects the schedule you configure; WhatsApp does not expose agent presence to a website. Treat it as a hint about expected response time rather than live availability.

Yes, with no special detection code. On mobile it opens the native WhatsApp app if installed; on desktop it opens WhatsApp Web in a new tab, or the desktop app if the OS handles wa.me links.

Dismissing it stores a flag in sessionStorage, so it stays closed for the rest of the visit. Switch to localStorage in wasDismissed() and closeBubble() to remember the choice across visits, or set showAfterMs very high or remove the setTimeout line to never auto-open it.

Yes. The button is a real <a> element with a descriptive aria-label, reachable via Tab, with a visible focus-visible outline. The bubble is a labelled dialog, can be closed with its button or the Escape key, and decorative icons are hidden from assistive technology.

Yes. Add a scroll listener that toggles a hidden class on .wa-widget based on window.scrollY, the same pattern used by a back-to-top button.