DEV Community

Cover image for Create a Button with a Loading Spinner in HTML & CSS ๐Ÿ”ฅ
Dom (dcode)
Dom (dcode)

Posted on

Create a Button with a Loading Spinner in HTML & CSS ๐Ÿ”ฅ

In today's post I'll be showing you how to use HTML and CSS to create an awesome looking button that features a loading spinner when clicked on.

Source Code & Preview

If you want to follow a long with the complete source code, you can find it here.

Video Tutorial

If you prefer this tutorial in the form of a video, you can watch it on my YouTube channel, dcode.

Also, consider subscribing to my channel if you're interested in web development ๐Ÿ˜


Let's begin by writing the HTML for the button. This is going to be fairly straightforward. We create a <button> with a class of button and use a <span> for the text inside of it.

<button type="button" class="button">
    <span class="button__text">Save Changes</span>
Enter fullscreen mode Exit fullscreen mode

That's all we need for the HTML. Let's move onto the CSS ๐Ÿ™‚


Here's where the majority of the code is placed.

Styling The Button

To begin, let's style up the .button and .button-text class.

.button {
    position: relative;
    padding: 8px 16px;
    background: #009579;
    border: none;
    outline: none;
    border-radius: 2px;
    cursor: pointer;

.button:active {
    background: #007a63;

.button__text {
    font: bold 20px 'Quicksand', san-serif;
    color: #ffffff;
    transition: all 0.2s;
Enter fullscreen mode Exit fullscreen mode

As you may have noticed, a lot of these properties are for look and feel - but the main one to focus on here is position: relative.

By using position: relative, it means we can center the loading spinner, which we'll see shortly.

Creating The Spinner

We'll be using the ::after pseudo-element to create the spinning animation. A pseudo-element is an element (like HTML) that you can style in CSS - in our case, ::after will create a "fake element" that sits inside our .button.

We're going to be applying CSS to a class called .button--loading. Basically, this is a modifier class on the .button which can be added dynamically through JavaScript whenever you want the spinning animation to appear. For example, this may be done at the time of submitting a form.

.button--loading::after {
    content: "";
    position: absolute;
    width: 16px;
    height: 16px;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    border: 4px solid transparent;
    border-top-color: #ffffff;
    border-radius: 50%;
    animation: button-loading-spinner 1s ease infinite;
Enter fullscreen mode Exit fullscreen mode

Let's focus on a few properties here:

  • content: ""; - this is a requirement to get the spinner to display
  • position: absolute; - used in combination with position: relative; above, and the top, left, right and bottom properties will allow us to center the spinner
  • border: 4px solid transparent - with this, we are setting a 4px wide solid border that is transparent. Combining it with border-top-color will only show a border at the top of the 16x16 square. This is key to creating a circle spinner.
  • border-radius: 50% - this creates the circle

I recommend you toggle some of these properties on and off, to get a full understanding of how they work to produce a circle.

As you may have noticed, we're also specifying an animation here, with a value of button-loading-spinner 1s ease infinite. This means, as long as the spinner is showing, make it spin infinitely with an ease timing duration, and an animation time of 1 second.

The button-loading-spinner animation doesn't actually exist yet, so we must create it.


To make the animation specified in the previous block of code above work, we need to create keyframes for it. This is simple.

@keyframes button-loading-spinner {
    from {
        transform: rotate(0turn);

    to {
        transform: rotate(1turn);
Enter fullscreen mode Exit fullscreen mode

Here, we're just saying the animation must turn our quarter-circle spinner from no turn to a full turn. With this code, the animation property above will now work.

Making The Button Work

Now that we've got all the CSS done, we need to make the spinner appear using JavaScript. Typically, you'll want to activate the spinner as soon as the button is clicked on.

To do this, you simply add the class of .button--loading, for example:

const theButton = document.querySelector(".button");

theButton.addEventListener("click", () => {
Enter fullscreen mode Exit fullscreen mode

Every application is going to be different, so it's up to you to decide when to toggle the loading spinner. To remove it, you can use classList.remove("button--loading");.


And that is how to create a button with a loading spinner using HTML and CSS. If you have any questions or comments please leave them below and I'll try my best to respond ๐Ÿ™‚ cheers!

Top comments (6)

wattadozze profile image
Warren Bolton

Thank you for providing this information. For three months now I've been searching the web to find a simple solution (and understanding) of to how to incorporate a spinner into a formโ€™s submission process.
In 10 minutes, you explained in clear detail how to not only design the spinner but provide the facility to make it actionable within the submit button
A most grateful website building colleague

simpubgithub profile image

Nice work, Dom. ๐Ÿ˜€ This is great for buttons. How simple would it be to adopt this to links? I'd like every link on my site, when clicked, to show a spinner in the middle of the screen. I would just use an animated .gif in my case if I were able. Can this concept be universally applied to all links upon clicking? That would be sweet ๐Ÿ˜€


nouraali3 profile image

hello Dom,

Thanks for posting this post.
Can you please help me out how to configure the spinner such that when the button is clicked --> the spinner start --> the time consuming function starts and finishes --> spinner stop --> directed to the response page


Application Form


    <button type="button" class="button" onclick="this.classList.toggle('button--loading')">
        <span class="button__text">Submit</span>

and this is my

@app.route('/' , methods=['POST', 'GET'])
def index():
if request.method == 'POST':
name = request.form['name']
#this is when i need the spinner to spin for 5 mins
#perform some time consuming stuff
return redirect(url_for('response'))

return render_template('index.html')

mohsenreyhani profile image
Mohsen Reyhani

i guess you forgot to add this in your code:

.button--loading .button__text {
visibility: hidden;
opacity: 0;

hahacyd profile image
Chen Yadong

Thanks for your tutorial!

devhabibur1 profile image
Habibur Rahman

Thanks for this tutorial . really its awesome .