webdevpuneet.com

Mobile Voice Message Screen — Hold to Record, Slide Cancel

Mobile Voice Message Recording Screen · Mobile · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="vms-phone">
  <div class="vms-screen">
    <div class="vms-status"><span>9:41</span><span class="vms-batt"><i></i></span></div>
    <header class="vms-head">
      <button type="button" class="vms-back" aria-label="Back">&#8249;</button>
      <div class="vms-contact">
        <span class="vms-avatar">P</span>
        <div class="vms-who"><b>Priya</b><small>online</small></div>
      </div>
    </header>

    <div class="vms-thread" id="vmsThread">
      <div class="vms-day">Today</div>
      <div class="vms-msg vms-them">Hey! Can you send a quick voice note about the launch plan?</div>
      <div class="vms-msg vms-me">Sure, one sec</div>
    </div>

    <div class="vms-toast" id="vmsToast" role="status"></div>

    <div class="vms-composer" id="vmsComposer">
      <div class="vms-field">
        <input class="vms-input" id="vmsInput" placeholder="Message" aria-label="Message" autocomplete="off">
        <div class="vms-rec" aria-hidden="true">
          <span class="vms-rec-dot"></span>
          <span class="vms-timer" id="vmsTimer">0:00</span>
          <div class="vms-live-wave" id="vmsWave"></div>
          <span class="vms-hint" id="vmsHint"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg><span id="vmsHintText">Slide to cancel</span></span>
        </div>
      </div>
      <button type="button" class="vms-mic" id="vmsMic" aria-label="Hold to record a voice message">
        <svg class="vms-ic-mic" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 15a3 3 0 0 0 3-3V6a3 3 0 1 0-6 0v6a3 3 0 0 0 3 3Z"/><path d="M19 11a7 7 0 0 1-14 0M12 18v3"/></svg>
        <svg class="vms-ic-send" width="17" height="17" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M3 20 21 12 3 4v6l12 2-12 2z"/></svg>
      </button>
    </div>
  </div>
</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

Pointer-capture gesture
One set of pointer events covers mouse, touch and pen, and capture keeps the release arriving wherever the finger goes.
Real slide-to-cancel
Sliding 70px left turns the composer red and cancels on release, with the hint following the finger.
Accidental-tap guard
A press under 600ms sends nothing and shows a "Hold to record" toast.
Live waveform
A smooth random walk adds a bar every 90ms, trimmed to fit, with a blinking dot and a running timer.
Waveform from the recording
The sent bubble samples 24 of the recorded levels across the whole take.
Playable bubbles
Play and pause, bars fill as it plays, the time counts up and resets at the end.
Mic becomes Send
Typing swaps the mic icon for a send arrow that posts a text bubble.
Keyboard and reduced motion
Hold Space or Enter to record, Escape to cancel, and animations switch off when the user prefers less motion.

About this UI Snippet

Mobile Voice Message Screen — Hold-to-Record, Slide-to-Cancel and Playable Voice Bubbles

Screenshot of the Mobile Voice Message Recording Screen snippet rendered live

A voice note in a chat app is a gesture, not a button: you press and hold, speak, and either release to send or slide away to throw it out. This snippet builds that whole interaction inside a CSS phone frame in plain HTML, CSS and vanilla JavaScript. Holding the mic swaps the text field for a live timer and waveform, sliding left turns the composer red and cancels on release, and letting go sends a voice bubble into the thread whose waveform is the shape of what you recorded and which actually plays back.

One mic button that never moves

An earlier version hid the mic button while recording, which meant the release event had nowhere to land and the recording could end as soon as the pointer moved. Here the button stays put, scales up with a pulsing ring, and owns the whole gesture through pointer events. pointerdown starts it and calls setPointerCapture, so pointermove and pointerup keep arriving even when the finger drifts off the button; pointercancel discards. One set of handlers covers mouse, touch and pen.

Slide to cancel, for real

pointermove measures how far the pointer has travelled left of where it started. The hint text follows at half speed, and once the distance passes 70px the composer gets an is-cancel class: the field and mic turn red and the hint reads "Release to cancel". Releasing there discards the take with a short "Recording discarded" toast; sliding back before releasing sends normally.

Accidental taps and the text-to-send swap

A press shorter than 600ms is treated as a mistake: nothing is sent and a toast explains "Hold to record, release to send". When there is text in the field, the mic icon becomes a send arrow and the same button posts the message as a normal bubble, which is how the major chat apps behave.

A live waveform with a memory

Every 90ms a bar is added using a smooth random walk, so the levels rise and fall like speech, and the oldest bars are trimmed to fit the width. The levels are also stored. When you send, the bubble samples 24 of them across the whole recording, so its waveform shows the shape of that recording rather than just the last second.

Bubbles that play

The play button toggles between play and pause icons, fills the bars from left to right as time passes, counts up the duration and resets when it ends. Swap the timer for an <audio> element and its timeupdate event when you add real audio.

Keyboard and motion

Hold Space or Enter on the mic to record, release to send, press Escape or move focus away to cancel. The pulse, blinking dot and entrance animations switch off with prefers-reduced-motion.

Going live

The waveform and timing are simulated. To record real audio, request navigator.mediaDevices.getUserMedia({ audio: true }), feed the stream to a MediaRecorder and an AnalyserNode for the levels, and attach the resulting Blob to the bubble. Put the composer inside a mobile chat screen, or compare it with the standalone voice message bubble.

Step by step

How to Use

  1. 1
    Paste HTML, CSS and JSA chat screen appears with an input field and a purple mic button; the thread has a date chip and two messages.
  2. 2
    Press and hold the micThe input is replaced by a red dot, a running timer and a live waveform, and the mic scales up with a pulsing ring.
  3. 3
    Slide leftPast 70px the composer turns red and the hint reads "Release to cancel". Releasing there discards the take with a toast.
  4. 4
    Release in placeA voice bubble appears whose waveform was sampled from your recording, with its duration. A press shorter than 0.6s sends nothing.
  5. 5
    Play the bubbleThe button switches to pause, the bars fill left to right and the time counts up, then resets at the end.
  6. 6
    Type a messageWith text in the field the mic becomes a send arrow, and pressing it posts a normal text bubble.

Real-world uses

Common Use Cases

Chat and messaging apps
Use this composer inside a mobile chat screen; the gesture, cancel zone and playable bubble are what users expect from voice notes.
Support and customer replies
Let agents or customers answer a ticket by voice, with the cancel gesture preventing half-recorded messages from going out by mistake.
Coaching and feedback apps
Record short spoken feedback on a lesson or task, with the duration and waveform telling the recipient how long it is before pressing play.
Field and logging tools
Capture a quick note one-handed with hold-to-record, then review it with the standalone voice message bubble pattern.
Learning press-and-hold
A compact reference for pointer capture, a cancel threshold and a minimum-duration guard, the three details that make hold gestures reliable on touch.

Got questions?

Frequently Asked Questions

Pointer events give mouse, touch and pen one set of handlers, and setPointerCapture keeps pointermove and pointerup coming to the mic even when the finger slides off it. That removes the stuck-recording bugs that come from mouseleave and separate touchend handling.

On every pointermove it subtracts the starting x from the current x and ignores movement to the right. If the result passes minus 70px, the composer gets the is-cancel class, and releasing in that state calls finish(false) instead of sending. Sliding back before releasing sends normally.

No, the levels are a simulated random walk so the demo works without microphone permission. For real levels, connect getUserMedia to an AnalyserNode and push its amplitude into the levels array on each tick. Recording the Blob with MediaRecorder gives you the audio to play.

Create an audio element from the recorded Blob with URL.createObjectURL, then call play and pause on it from the play button, and set the bars from its timeupdate event instead of the interval timer. Uploading the Blob to your server gives the recipient the same bubble.