Learn how to create a recording toggle button using HTML, CSS, and JavaScript with easy steps. Build a simple and responsive toggle switch for your web projects.
Table of Contents
In this guide, we will walk you through creating a recording toggle switch button using HTML, CSS, and JavaScript. A toggle button lets users switch between two states, such as start and stop recording. This type of button is useful for video or voice recording features in websites. We will use simple coding techniques to ensure this is easy to understand for both beginners and intermediate developers.
Prerequisites
Before you start, make sure you have the following:
- Basic understanding of HTML and CSS – You should know how to structure a webpage and style elements.
- JavaScript basics – You need to understand how functions, events, and DOM manipulation work.
- Text Editor – You can use any code editor like VS Code, Sublime Text, or Notepad++.
- Web Browser – Google Chrome or Mozilla Firefox to test your code.
Source Code
Step 1 (HTML Code):
The first thing we need is the basic HTML layout. Here's a breakdown of the key parts:
1. DOCTYPE and <html>
Element:
<!DOCTYPE html>
<html lang="en">
<!DOCTYPE html>
: Defines that the document is an HTML5 document.<html lang="en">
: Specifies the language of the document as English.
2. Head Section:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recording Toggle</title>
<link rel="stylesheet" href="styles.css">
</head>
<meta charset="UTF-8">
: Defines the character encoding (UTF-8) for the webpage.<meta name="viewport" content="width=device-width, initial-scale=1.0">
: Ensures the webpage is responsive, especially on mobile devices.<title>Recording Toggle</title>
: Sets the title of the webpage as "Recording Toggle."<link rel="stylesheet" href="styles.css">
: Links an external CSS file calledstyles.css
to style the page.
3. Body Section:
<body>
<button id="recorder" class="recorder" type="button" aria-pressed="false">
<button>
: This is the main interactive element of the page, styled as a toggle button for recording. The button has:id="recorder"
: Used to identify this button for JavaScript manipulation.class="recorder"
: Applies styles from CSS.type="button"
: Specifies it's a button element.aria-pressed="false"
: A state attribute indicating that the button is not pressed initially (used for accessibility).
4. Button Label (Stop):
<span class="recorder__label-start" aria-hidden="true">Stop</span>
<span>
: A label for the button that says "Stop". This is hidden (aria-hidden="true"
) for accessibility purposes, meaning it's not read by screen readers.
5. Toggle Switch and Timer Display:
<span class="recorder__switch">
<span class="recorder__switch-handle">
<svg class="recorder__timer" viewBox="0 0 16 16" width="16px" height="16px" aria-hidden="true">
<g fill="none" stroke-linecap="round" stroke-width="0" transform="rotate(-90,8,8)">
<circle class="recorder__timer-ring" stroke="hsla(0,0%,100%,0.3)" cx="8" cy="8" r="8"></circle>
<circle class="recorder__timer-ring" id="timer-ring" stroke="hsla(0,0%,100%,0.5)" cx="8" cy="8" r="8"></circle>
</g>
</svg>
</span>
</span>
<svg>
: This is an inline SVG graphic that represents a timer. It has:viewBox="0 0 16 16"
: Defines the viewable area of the SVG.<circle>
: Two circular rings are drawn, one of which has the IDtimer-ring
, and they represent a part of the timer's visual.
6. Button Label (Record) and Timer:
<span class="recorder__label-end" aria-hidden="true">
<span class="recorder__label-end-text">Record</span>
<span class="recorder__label-end-text" id="time">00:00</span>
</span>
<span class="recorder__label-end">
: This span represents the label when the button is in the "Record" state. Inside it:<span class="recorder__label-end-text">Record</span>
: Shows the text "Record".<span class="recorder__label-end-text" id="time">00:00</span>
: This is a time display, initialized to00:00
, indicating that it might update as the recording progresses.
7. Script Link:
<script src="script.js"></script>
- Links to an external JavaScript file (
script.js
) that will handle the functionality of the toggle button, such as starting/stopping the recording and updating the timer.
Step 2 (CSS Code):
To make the toggle button look good, we need to style it using CSS. Here's a breakdown of the CSS code:
Global Reset (CSS Reset)
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
*
: Targets all elements on the page, resetting border, margin, and padding to0
.box-sizing: border-box
ensures that padding and borders are included in the element’s total width and height.
Root Variables (:root
)
:root {
--hue: 223;
--red: hsl(3, 90%, 50%);
--white: hsl(0, 0%, 100%);
--primary: hsl(var(--hue), 90%, 50%);
--primary-t: hsla(var(--hue), 90%, 50%, 0);
--gray1: hsl(var(--hue), 10%, 90%);
--gray3: hsl(var(--hue), 10%, 70%);
--gray9: hsl(var(--hue), 10%, 10%);
--trans-dur: 0.3s;
--trans-timing: cubic-bezier(0.65, 0, 0.35, 1);
font-size: calc(28px + (60 - 28) * (100vw - 320px) / (3840 - 320));
}
:root
: Defines CSS custom properties (variables) that can be reused throughout the CSS.--hue
: The base hue for the primary color (set to 223).--red
: A specific red color using HSL (Hue-Saturation-Lightness) values.--white
: White color using HSL.--primary
: The primary color using the hue value.--primary-t
: Transparent version of the primary color.--gray1
,--gray3
,--gray9
: Shades of gray using the same hue.--trans-dur
: Transition duration for animations (0.3 seconds).--trans-timing
: Custom timing function for transitions (cubic-bezier).font-size
: Responsive font size that adjusts based on viewport width (100vw
).
Styling for Body and Button
body, button {
color: var(--gray9);
font: 1em/1.5 "DM Sans", sans-serif;
transition: background-color var(--trans-dur), color var(--trans-dur);
}
body {
background-color: var(--gray1);
}
body, button
: Both the body and buttons inherit a color (--gray9
), font family (DM Sans
), and transition effects for background and color changes.body
: The background color is set to--gray1
.
Recorder Button (.recorder
)
.recorder {
background-color: transparent;
cursor: pointer;
display: flex;
align-items: center;
margin: auto;
outline: transparent;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-webkit-appearance: none;
appearance: none;
-webkit-tap-highlight-color: transparent;
}
.recorder
: A button styled as a recording element.background-color: transparent
: No background color.cursor: pointer
: Indicates it's clickable.display: flex
: Aligns child elements in a flexbox.align-items: center
: Centers the items vertically.position: absolute
,transform
: Centers the button on the page usingtranslate(-50%, -50%)
.
Labels for Start and End
.recorder__label-start, .recorder__label-end {
display: block;
position: relative;
}
.recorder__label-start, .recorder__label-end-text {
transition: opacity var(--trans-dur);
}
.recorder__label-start
,.recorder__label-end
: Block elements with relative positioning for potential inner adjustments.- Transition is applied to the opacity, making them fade in and out smoothly.
Switch Button (.recorder__switch
)
.recorder__switch {
background-color: var(--white);
border-radius: 0.75em;
box-shadow: 0 0 0 0.125em var(--primary-t), 0 0.25em 0.25em rgba(0, 0, 0, 0.1);
display: flex;
padding: 0.25em;
width: 2.5em;
height: 1.5em;
}
.recorder__switch
: Styled as a toggle switch.border-radius: 0.75em
: Gives it rounded edges.box-shadow
: Adds depth with shadows.width: 2.5em
,height: 1.5em
: Defines the size of the switch.
Switch Handle (.recorder__switch-handle
)
.recorder__switch-handle {
background-color: var(--gray3);
border-radius: 50%;
display: block;
transform-origin: 0 0.5em;
width: 1em;
height: 1em;
}
.recorder__switch-handle
: This is the circular part of the toggle switch.transform-origin: 0 0.5em
: Sets the pivot point for transformations.background-color: var(--gray3)
: Initially set to gray.
Timer (.recorder__timer
)
.recorder__timer {
display: block;
overflow: visible;
width: 100%;
height: auto;
}
.recorder__timer
: Styles for the timer element.width: 100%
: Takes up full width of the parent.
Interactions for Pressed State (aria-pressed=true
)
.recorder[aria-pressed=true] .recorder__switch-handle {
background-color: var(--red);
transform: translateX(100%);
}
.recorder[aria-pressed=true]
: When the recorder button is pressed (aria-pressed=true
).- Changes the
background-color
to red. - Moves the switch handle to the right using
transform: translateX(100%)
.
- Changes the
Focus and Active States
.recorder:focus-visible .recorder__switch {
box-shadow: 0 0 0 0.125em var(--primary), 0 0.25em 0.25em rgba(0, 0, 0, 0.1);
}
.recorder:active .recorder__switch-handle {
transform: scaleX(1.5);
}
.recorder:focus-visible
: Adds focus styling with a shadow for accessibility..recorder:active
: : On pressing, scales the switch handle horizontally to give a click effect.
RTL (Right-to-Left) Support
[dir=rtl] .recorder__switch-handle {
transform-origin: 100% 0.5em;
}
[dir=rtl]
: Supports right-to-left layouts. Elements will switch direction when the language direction is set to RTL.
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--hue: 223;
--red: hsl(3,90%,50%);
--white: hsl(0,0%,100%);
--primary: hsl(var(--hue),90%,50%);
--primary-t: hsla(var(--hue),90%,50%,0);
--gray1: hsl(var(--hue),10%,90%);
--gray3: hsl(var(--hue),10%,70%);
--gray9: hsl(var(--hue),10%,10%);
--trans-dur: 0.3s;
--trans-timing: cubic-bezier(0.65,0,0.35,1);
font-size: calc(28px + (60 - 28) * (100vw - 320px) / (3840 - 320));
}
body,
button {
color: var(--gray9);
font: 1em/1.5 "DM Sans", sans-serif;
transition: background-color var(--trans-dur), color var(--trans-dur);
}
body {
background-color: var(--gray1);
}
.recorder {
background-color: transparent;
cursor: pointer;
display: flex;
align-items: center;
margin: auto;
outline: transparent;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-webkit-appearance: none;
appearance: none;
-webkit-tap-highlight-color: transparent;
}
.recorder__label-start, .recorder__label-end {
display: block;
position: relative;
}
.recorder__label-start, .recorder__label-end-text {
transition: opacity var(--trans-dur);
}
.recorder__label-start {
margin-inline: 0 0.5em;
}
.recorder__label-end {
margin-inline: 0.5em 0;
}
.recorder__label-end-text {
opacity: 0.4;
}
.recorder__label-end-text + .recorder__label-end-text {
opacity: 0;
position: absolute;
top: 0;
left: 0;
}
[dir=rtl] .recorder__label-end-text + .recorder__label-end-text {
right: 0;
left: auto;
}
.recorder__switch {
background-color: var(--white);
border-radius: 0.75em;
box-shadow: 0 0 0 0.125em var(--primary-t), 0 0.25em 0.25em rgba(0, 0, 0, 0.1);
display: flex;
padding: 0.25em;
width: 2.5em;
height: 1.5em;
}
.recorder__switch, .recorder__switch-handle {
transition: background-color var(--trans-dur), box-shadow var(--trans-dur), transform var(--trans-dur) var(--trans-timing), transform-origin var(--trans-dur) var(--trans-timing);
}
.recorder__switch-handle {
background-color: var(--gray3);
border-radius: 50%;
display: block;
transform-origin: 0 0.5em;
width: 1em;
height: 1em;
}
[dir=rtl] .recorder__switch-handle {
transform-origin: 100% 0.5em;
}
.recorder__timer {
display: block;
overflow: visible;
width: 100%;
height: auto;
}
.recorder__timer-ring {
transition: r var(--trans-dur) var(--trans-timing), stroke-dasharray var(--trans-dur) var(--trans-timing), stroke-dashoffset var(--trans-dur) var(--trans-timing), stroke-width var(--trans-dur) var(--trans-timing);
}
.recorder:focus-visible .recorder__switch {
box-shadow: 0 0 0 0.125em var(--primary), 0 0.25em 0.25em rgba(0, 0, 0, 0.1);
}
.recorder:active .recorder__switch-handle {
transform: scaleX(1.5);
}
.recorder[aria-pressed=true] .recorder__label-start {
opacity: 0.4;
}
.recorder[aria-pressed=true] .recorder__label-end-text {
opacity: 0;
}
.recorder[aria-pressed=true] .recorder__label-end-text + .recorder__label-end-text {
opacity: 1;
}
.recorder[aria-pressed=true] .recorder__switch-handle {
background-color: var(--red);
transform: translateX(100%);
transform-origin: 100% 0.5em;
}
[dir=rtl] .recorder[aria-pressed=true] .recorder__switch-handle {
transform: translateX(-100%);
transform-origin: 0 0.5em;
}
.recorder[aria-pressed=true] .recorder__timer-ring {
r: 6.5px;
stroke-width: 3px;
}
.recorder[aria-pressed=true]:active .recorder__switch-handle {
transform: translateX(100%) scaleX(1.5);
}
[dir=rtl] .recorder[aria-pressed=true]:active .recorder__switch-handle {
transform: translateX(-100%) scaleX(1.5);
}
Step 3 (JavaScript Code):
Next, we add functionality to our button using JavaScript. We will change the state of the button and the visual appearance based on whether it’s recording or not. Let's break down the JavaScript code:
DOMContentLoaded
Event:- The code runs only after the entire HTML document is fully loaded. This ensures that elements like buttons and timers are available to interact with.
- Variable Declarations:
recorderButton
: Refers to the button that starts/stops the recording.timeDisplay
: Shows the timer on the screen in a "mm" format.
timerRing
: A ring element (possibly an SVG circle) that visually displays the remaining time.timeMax
: Sets the maximum recording time (60 seconds).recording
: Boolean to check if the recording is active or not.time
: Keeps track of the elapsed recording time.timeStopped
: Tracks the time for when the recording stops.intervalId
: Stores the interval that updates the timer every second.
updateTimerDisplay()
Function:- This function converts the elapsed time (
time
) into minutes and seconds, formats them asmm:ss
, and displays it intimeDisplay
.
- This function converts the elapsed time (
updateProgress()
Function:- This function controls the visual progress on
timerRing
. It adjusts the circumference of the ring based on whether the recording is running or stopped. - The
circumferencePart
is calculated as a percentage of time left for the timer ring. When the recording is active, it reduces the visual stroke of the ring.
- This function controls the visual progress on
startRecording()
Function:- Resets
time
andtimeStopped
to 0. - Calls
updateTimerDisplay()
andupdateProgress()
to reflect the reset state. - Starts an interval (runs every second) that:
- Increments
time
andtimeStopped
. - Updates the timer display and progress.
- If
time
exceedstimeMax
(60 seconds), it automatically stops the recording.
- Increments
- Resets
stopRecording()
Function:- Clears the interval to stop the timer from updating further.
- Calls
updateProgress()
to visually update the timer ring when the recording stops.
- Event Listener for Button:
- The
recorderButton
has a click event listener that toggles the recording state (start or stop). - When clicked, the
recording
state switches (true
orfalse
), and the button'saria-pressed
attribute updates accordingly to reflect its state. - If
recording
becomestrue
, it starts the timer; iffalse
, it stops it.
- The
document.addEventListener('DOMContentLoaded', function () {
const recorderButton = document.getElementById('recorder');
const timeDisplay = document.getElementById('time');
const timerRing = document.getElementById('timer-ring');
const timeMax = 60;
let recording = false;
let time = 0;
let timeStopped = 0;
let intervalId;
function updateTimerDisplay() {
const minutes = `0${Math.floor(time / 60)}`.slice(-2);
const seconds = `0${time % 60}`.slice(-2);
timeDisplay.textContent = `${minutes}:${seconds}`;
}
function updateProgress() {
const circumference = recording ? 40.84 : 50.27;
const circumferencePart = recording ? 1 - (time / timeMax) : 1;
const strokeDashArray = `${circumference} ${circumference}`;
const strokeDashOffset = +(circumference * circumferencePart).toFixed(2);
timerRing.style.strokeDasharray = strokeDashArray;
timerRing.style.strokeDashoffset = strokeDashOffset;
}
function startRecording() {
time = 0;
timeStopped = 0;
updateTimerDisplay();
updateProgress();
intervalId = setInterval(() => {
time++;
timeStopped++;
updateTimerDisplay();
updateProgress();
if (time >= timeMax) {
stopRecording();
}
}, 1000);
}
function stopRecording() {
clearInterval(intervalId);
updateProgress();
}
recorderButton.addEventListener('click', function () {
recording = !recording;
recorderButton.setAttribute('aria-pressed', recording);
if (recording) {
startRecording();
} else {
stopRecording();
}
});
});
Final Output:
Conclusion:
You have now successfully created a recording toggle button using HTML, CSS, and JavaScript. This tutorial demonstrated how to switch between two states and add animations to your button for a better user experience. You can also extend this project by adding a timer or even audio recording functionality. Whether you are building a recording tool for a website or simply learning web development, this toggle button is a great addition to your project.
Feel free to experiment with different styles and features to make this recording toggle button even more interactive and useful. With just a few lines of code, you’ve created a powerful and responsive toggle switch that can be implemented in many real-world projects.
By following this step-by-step guide, you now have a working toggle button that can be used for recording or any other similar feature.
Inspired by: Jon Kantner
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊