Introduction:
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>
: Defines that the document is an HTML5 document.<html lang="en">
: Specifies the language of the document as English.
2. Head Section:
Recording Toggle
<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 calledÂstyles.css
 to style the page.
3. Body Section:
<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):
Stop
<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:
<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 IDÂtimer-ring
, and they represent a part of the timer’s visual.
6. Button Label (Record) and Timer:
Record
00:00
<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 toÂ00:00
, indicating that it might update as the recording progresses.
7. Script Link:
- 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.
Recording Toggle Switch Button
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 toÂ0
.Â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 usingÂtranslate(-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 asÂmm:ss
, and displays it inÂtimeDisplay
.
- 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
 andÂtimeStopped
 to 0. - CallsÂ
updateTimerDisplay()
 andÂupdateProgress()
 to reflect the reset state. - Starts an interval (runs every second) that:
- IncrementsÂ
time
 andÂtimeStopped
. - Updates the timer display and progress.
- IfÂ
time
 exceedsÂtimeMax
 (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
 orÂfalse
), and the button’sÂaria-pressed
 attribute updates accordingly to reflect its state. - IfÂ
recording
 becomesÂtrue
, it starts the timer; ifÂfalse
, it stops it.
- TheÂ
console.log( 'Code is Poetrydocument.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.