Create a Morphing Submit Button with HTML, CSS, and JS


By Faraz -

Learn how to create an engaging Morphing Submit Button for your web forms. Follow our step-by-step guide and add interactivity to your website using HTML, CSS, and JavaScript.

how to create morphing submit button using css.png

Table of Contents

  1. Project Introduction
  2. HTML Code
  3. CSS Code
  4. JavaScript Code
  5. Preview
  6. Conclusion

A Morphing Submit Button is a type of button used in web forms that change its shape, size, or color when the user interacts with it, providing a more engaging and interactive user experience. It can be achieved using HTML, CSS, and JavaScript, and can be customized to fit the look and feel of any website. By enhancing the design and interactivity of web forms, Morphing Submit Buttons can help to improve user engagement and increase conversions.

Let's start making these amazing Morphing Submit Button Using HTML, CSS, and Vanilla JavaScript step by step.

Join My Telegram Channel to Download the Project: Click Here


Before starting this tutorial, you should have a basic understanding of HTML, CSS, and JavaScript. Additionally, you will need a code editor such as Visual Studio Code or Sublime Text to write and save your code.

Source Code

Step 1 (HTML Code):

To get started, we will first need to create a basic HTML file. In this file, we will include the main structure for our morphing button.

The button element has a class of "btn" and a type of "button". Inside the button element, there is a span element with a class of "btn__text" that contains the text "Submit".

Next, there is an SVG element with a class of "btn__progress" that creates the progress indicator. It has two circle elements inside it, one with a class of "btn__progress-track" and another with a class of "btn__progress-fill". The "btn__progress-track" circle creates a gray circle with a stroke width of 8. The "btn__progress-fill" circle creates a black circle with a stroke width of 8 and a dash array and dash offset that create the animation effect.

Finally, there is a polyline element with a class of "btn__progress-check" that creates a checkmark when the progress is complete. It has a stroke width of 4 and a dash array and dash offset that create the animation effect.

After creating the files just paste the following below codes into your file. Make sure to save your HTML document with a .html extension, so that it can be properly viewed in a web browser.

This is the basic structure of our morphing button using HTML, and now we can move on to styling it using CSS.

Step 2 (CSS Code):

Once the basic HTML structure of the button is in place, the next step is to add styling to the submit button using CSS.

Next, we will create our CSS file. In this file, we will use some basic CSS rules to create our morphing effect. We will also add some padding and margin properties to ensure that everything looks correct.

The below CSS code block defines the styling for a button element. It includes several CSS properties that define the appearance and behavior of the button. Here's a breakdown of what the code does:

The first rule selects all elements and sets their border, box-sizing, margin, and padding to zero, which is a common CSS reset to ensure consistent styling across different browsers and devices.

The :root selector sets some custom CSS variables, which can be used throughout the stylesheet to define colors, sizes, and other values. The --hue variable is set to 223, which is a hue value in the HSL color space. The other variables are defined using this hue value, and specify different shades of the primary color, a font size that adjusts to the viewport width, and other styling options.

The body and button selectors set the font to Nunito, a sans-serif font, with a font size of 1em and a line height of 1.5em.

The body selector sets the background color, text color, height, and grid display properties, which center the button on the screen.

The .btn selector defines the main styles for the button, including a transparent background color, rounded corners, and a width and height of 7.5em and 3em, respectively. It also sets a transition effect for the button width when it's hovered over or clicked.

The .btn__text selector styles the text inside the button, setting a background color that corresponds to the primary color defined earlier, white text color, and rounded corners that inherit the border radius of the parent element. It also sets a transition effect for the background color and text color when the button is hovered over or clicked.

The .btn__progress selector defines an SVG element that will display a progress circle when the button is clicked. It's initially hidden and positioned on top of the button using absolute positioning.

The .btn__progress-track selector styles the outer ring of the progress circle, setting its stroke color, stroke width, and radius.

The .btn__progress-fill selector styles the progress circle itself, setting its stroke color, stroke width, stroke-dasharray, and stroke-dashoffset. These properties are used to animate the progress circle and create a spinning effect when the button is clicked.

The .btn__progress-check selector defines a polyline that creates a checkmark shape inside the progress circle when the button animation is complete.

The .btn--running and .btn--done selectors define styles for the button when it's in the running or done state, respectively. These states are used to control the animation and visibility of the progress circle and checkmark.

The @media (prefers-color-scheme: dark) selector defines a dark theme for the button by adjusting the background and text colors using the --bg1, --bg2, --fg1, and --fg2 variables. This theme is applied when the user's device is set to a dark color scheme.

This will give our morphing button an upgraded presentation. Create a CSS file with the name of styles.css and paste the given codes into your CSS file. Remember that you must create a file with the .css extension.

* {
	border: 0;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
:root {
	--hue: 223;
	--bg1: #cebfbf;
	--bg2: hsl(var(--hue),10%,80%);
	--fg1: hsl(var(--hue),10%,10%);
	--fg2: hsl(var(--hue),10%,20%);
	--primary1: hsl(var(--hue),90%,55%);
	--primary2: hsl(var(--hue),90%,45%);
	font-size: calc(20px + (40 - 20) * (100vw - 320px) / (1280 - 320));
body, button {
	font: 1em/1.5 Nunito, sans-serif;
body {
	background-color: var(--bg1);
	color: var(--fg1);
	height: 100vh;
	display: grid;
	place-items: center;

/* Main button styles */
.btn {
	background-color: transparent;
	border-radius: 1.5em;
	display: block;
	position: relative;
	width: 7.5em;
	height: 3em;
	transition: width 0.3s ease-in-out;
.btn:not(:disabled):active {
	transform: translateY(0.1em);
.btn__text {
	background-color: var(--primary1);
	border-radius: inherit;
	color: hsl(0,0%,100%);
	display: inline-block;
	padding: 0.75em 1.5em;
		background-color 0.15s linear,
		color 0.15s 0.3s ease-in-out;
	width: 100%;
.btn:not(:disabled):focus .btn__text,
.btn:not(:disabled):hover .btn__text {
	background-color: var(--primary2);
.btn__progress {
	overflow: visible;
	position: absolute;
	top: 0;
	left: 0;
	width: 3em;
	height: 3em;
	visibility: hidden;
.btn__progress-track {
	r: 12px;
	stroke: var(--bg2);
	stroke-width: 24;
.btn__progress-fill {
	stroke: var(--primary1);
	stroke-dashoffset: 125.66;
.btn__progress-check {
	stroke: hsl(0,0%,100%);
	stroke-dashoffset: 34;

/* Both states */
.btn--done {
	outline: none;
	pointer-events: none;
	width: 3em;
	user-select: none;
	-moz-user-select: none;
	-webkit-user-select: none;
.btn--running .btn__text,
.btn--done .btn__text {
	color: transparent;
		background-color 0.3s ease-in-out,
		visibility 0.3s steps(1);
.btn--running .btn__progress,
.btn--done .btn__progress {
	visibility: visible;

/* Running state */
.btn--running .btn__text {
	background-color: var(--bg2);
	visibility: hidden;
.btn--running .btn__progress {
	transition: visibility 0.3s 0.3s steps(1,start);
.btn--running .btn__progress-track {
	r: 20px;
	stroke-width: 8;
		r 0.3s 0.3s ease-in-out,
		stroke-width 0.3s 0.3s ease-in-out;
.btn--running .btn__progress-fill {
	stroke-dashoffset: 0;
	transition: stroke-dashoffset 2s 0.6s linear;

/* Done state */
.btn--done .btn__progress-track {
	stroke: var(--primary1);
		r 0.3s ease-in-out,
		stroke-width 0.3s ease-in-out;
.btn--done .btn__progress-check {
	stroke-dashoffset: 0;
	transition: stroke-dashoffset 0.3s 0.3s ease-out;

/* Dark theme */
@media (prefers-color-scheme: dark) {
	:root {
		--bg1: hsl(var(--hue),10%,10%);
		--bg2: hsl(var(--hue),10%,20%);
		--fg1: hsl(var(--hue),10%,90%);
		--fg2: hsl(var(--hue),10%,80%);

Step 3 (JavaScript Code):

Finally, we need to add JavaScript into our button.

The JavaScript code adds an event listener to the DOMContentLoaded event of the window object. When the DOM has loaded, it selects the first button element on the page and attaches a click event listener to it. When the button is clicked, the following happens:

  • The button is disabled and given a btn--running class to indicate that the submission process has started.
  • Two timeouts are cleared to ensure that they don't interfere with the current submission process.
  • A new timeout is created that simulates a submission process that takes submitDuration milliseconds (2 seconds) to complete.
  • After 600ms, the btn--running class is removed and replaced with a btn--done class to indicate that the submission process is complete.
  • Another timeout is created to reset the button after resetDuration milliseconds (1.5 seconds). The disabled property is set back to false, and the btn--done class is removed.

The purpose of this code is to simulate a button click that triggers a submission process, and then visually indicates that the submission process has been completed successfully. The setTimeout() function is used to create a fake delay to simulate the submission process, and then to add a delay between the end of the submission process and the resetting of the button.

Create a JavaScript file with the name of script.js and paste the given codes into your JavaScript file and make sure it's linked properly to your HTML document, so that the scripts are executed on the page. Remember, you’ve to create a file with .js extension.

window.addEventListener("DOMContentLoaded",() => {
	const btn = document.querySelector("button");
	var doneTimeout = null,
		resetTimeout = null;

	if (btn) {
		btn.addEventListener("click",function() {
			const runClass = "btn--running";
			const doneClass = "btn--done";
			// `.btn--running .btn__progress-fill` `stroke-dashoffset` duration in ms
			const submitDuration = 2000;
			const resetDuration = 1500;

			// fake the submission
			this.disabled = true;


			doneTimeout = setTimeout(() => {
				// reset the button
				resetTimeout = setTimeout(() => {
					this.disabled = false;
				}, resetDuration);

			}, 600 + submitDuration);

Final Output:

how to create morphing submit button using css.gif


Congratulations, you've created a Morphing Submit Button with HTML, CSS, and JS! You can now add this button to your web forms and enhance the user experience of your website. Feel free to experiment with different colors, shapes, and transitions to create your own version of the button.

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!

Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox

Latest Post