Styling Buttons in WordPress Block Themes

A little while back, Ganesh Dahal penned a post here on CSS-Tricks responding to a tweet that asked about adding CSS box shadows on WordPress blocks and elements. There’s a lot of great stuff in there that leverages new features that shipped in WordPress 6.1 that provide controls for applying shadows to things directly in the Block Editor and Site Editor UI.

Ganesh touched briefly on button elements in that post. I want to pick that up and go deeper into approaches for styling buttons in WordPress block themes. Specifically, we’re going to crack open a fresh theme.json file and break down various approaches to styling buttons in the schema.

Why buttons, you ask? That’s a good question, so let’s start with that.

The different types of buttons

When we’re talking about buttons in the context of the WordPress Block Editor, we have to distinguish between two different types:

  1. Child blocks inside of the Buttons block
  2. Buttons that are nested inside other block (e.g. the Post Comments Form block)

If we add both of these blocks to a template, they have the same look by default.

A black button above a comment form that also contains a black button.

But the markup is very different:

<div class="wp-block-button">
  <a class="wp-block-button__link wp-element-button">Button 1</a>
</div>
<p class="form-submit wp-block-button">
  <input name="submit" type="submit" id="submit" class="wp-block-button__link wp-element-button" value="Post Comment"> 
</p>

As we can see, the HTML tag names are different. It’s the common classes — .wp-block-button and .wp-element-button — that ensure consistent styling between the two buttons.

If we were writing CSS, we would target these two classes. But as we know, WordPress block themes have a different way of managing styles, and that’s through the theme.json file. Ganesh also covered this in great detail, and you’d do well giving his article a read.

So, how do we define button styles in theme.json without writing actual CSS? Let’s do it together.

Creating the base styles

theme.json is a structured set of schema written in property:value pairs. The top level properties are called “sections”, and we’re going to work with the styles section. This is where all the styling instructions go.

We’ll focus specifically on the elements in the styles. This selector targets HTML elements that are shared between blocks. This is the basic shell we’re working with:

// theme.json
{
  "version": 2,
  "styles": {
    "elements": {
      // etc.
    }
  }
}

So what we need to do is define a button element.

={
  "version": 2,
  "styles": {
    "elements": {
      "button": {
        // etc.
      }
    }
  }
}

That button corresponds to HTML elements that are used to mark up button elements on the front end. These buttons contain HTML tags that could be either of our two button types: a standalone component (i.e. the Button block) or a component nested within another block (e.g. the Post Comment block).

Rather than having to style each individual block, we create shared styles. Let’s go ahead and change the default background and text color for both types of buttons in our theme. There’s a color object in there that, in turn, supports background and text properties where we set the values we want:

{
  "version": 2,
  "styles": {
    "elements": {
      "button": {
        "color": {
          "background": "#17a2b8",
          "text": "#ffffff"
        }
      }
    }
  }
}

This changes the color of both button types:

A light blue button above a comment form that also contains a light blue button.

If crack open DevTools and have a look at the CSS that WordPress generates for the buttons, we see that the .wp-element-button class adds the styles we defined in theme.json:

.wp-element-button {
  background-color: #17a2b8;
  color: #ffffff;
}

Those are our default colors! Next, we want to give users visual feedback when they interact with the button.

Implementing interactive button styles

Since this is a site all about CSS, I’d bet many of you are already familiar with the interactive states of links and buttons. We can :hover the mouse cursor over them, tab them into :focus, click on them to make them :active. Heck, there’s even a :visited state to give users a visual indication that they’ve clicked this before.

Those are CSS pseudo-classes and we use them to target a link’s or button’s interactions.

In CSS, we might style a :hover state like this:

a:hover {
  /* Styles */
}

In theme.json, we’re going to extend our existing button declaration with these pseudo-classes.

{
  "version": 2,
  "styles": {
    "elements": {
      "button": {
        "color": {
          "background": "#17a2b8",
          "text": "#ffffff"
        }
        ":hover": {
          "color": {
            "background": "#138496"
          }
        },
        ":focus": {
          "color": {
            "background": "#138496"
          }
        },
        ":active": {
          "color": {
            "background": "#138496"
          }
        }
      }
    }
  }
}

Notice the “structured” nature of this. We’re basically following an outline:

  • Elements
    • Element
      • Object
        • Property
          • Value

We now have a complete definition of our button’s default and interactive styles. But what if we want to style certain buttons that are nested in other blocks?

Styling buttons nested in individual blocks

Let’s imagine that we want all buttons to have our base styles, with one exception. We want the submit button of the Post Comment Form block to be blue. How would we achieve that?

This block is more complex than the Button block because it has more moving parts: the form, inputs, instructive text, and the button. In order to target the button in this block, we have to follow the same sort of JSON structure we did for the button element, but applied to the Post Comment Form block, which is mapped to the core/post-comments-form element:

{
  "version": 2,
  "styles": {
    "elements" {
      "button": {
        // Default button styles
      }
    }
    "blocks": {
      "core/post-comments-form": {
        // etc.
      }
    }
  }
}

Notice that we’re no longer working in elements anymore. Instead, we’re working inside blocks which is reserved for configuring actual blocks. Buttons, by contrast, are considered a global element since they can be nested in blocks, even though they are available as a standalone block too.

The JSON structure supports elements within elements. So, if there’s a button element in the Post Comment Form block, we can target it in the core/post-comments-form block:

{
  "version": 2,
  "styles": {
    "elements" {
      "button": {
        // Default button styles
      }
    }
    "blocks": {
      "core/post-comments-form": {
        "elements": {
          "button": {
            "color": {
              "background": "#007bff"
            }
          }
        }
      }
    }
  }
}

This selector means that not only are we targeting a specific block — we’re targeting a specific element that is contained in that block. Now we have a default set of button styles that are applied to all buttons in the theme, and a set of styles that apply to specific buttons that are contained in the Post Comment Form block.

A light blue button above a comment form that contans a bright blue button.

The CSS generated by WordPress has a more precise selector as a result:

.wp-block-post-comments-form .wp-element-button,
.wp-block-post-comments-form .wp-block-button__link {
  background-color: #007bff;
}

And what if we want to define different interactive styles for the Post Comment Form button? It’s the same deal as the way we did it for the default styles, only those are defined inside the core/post-comments-form block:

{
  "version": 2,
  "styles": {
    "elements" {
      "button": {
        // Default button styles
      }
    }
    "blocks": {
      "core/post-comments-form": {
        "elements": {
          "button": {
            "color": {
              "background": "#007bff"
            }
            ":hover": {
              "color": {
                "background": "#138496"
              }
            },
            // etc.
          }
        }
      }
    }
  }
}

What about buttons that are not in blocks?

WordPress automagically generates and applies the right classes to output these button styles. But what if you use a “hybrid” WordPress theme that supports blocks and full-site editing, but also contains “classic” PHP templates? Or what if you made a custom block, or even have a legacy shortcode, that contains buttons? None of these are handled by the WordPress Style Engine!

No worries. In all of those cases, you would add the .wp-element-button class in the template, block, or shortcode markup. The styles generated by WordPress will then be applied in those instances.

And there may be some situations where you have no control over the markup. For example, some block plugin might be a little too opinionated and liberally apply its own styling. That’s where you can typically go to the “Advanced” option in the block’s settings panel and apply the class there:

A WordPress block settings panel with the Advanced settings expanded highlighting the CSS classes section in red.

Wrapping up

While writing “CSS” in theme.json might feel awkward at first, I’ve found that it becomes second nature. Like CSS, there are a limited number of properties that you can apply either broadly or very narrowly using the right selectors.

And let’s not forget the three main advantages of using theme.json:

  1. The styles are applied to buttons in both the front-end view and the block editor.
  2. Your CSS will be compatible with future WordPress updates.
  3. The generated styles work with block themes and classic themes alike — there’s no need to duplicate anything in a separate stylesheet.

If you have used theme.json styles in your projects, please share your experiences and thoughts. I look forward to reading any comments and feedback!


Styling Buttons in WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Buttons vs. Links

There are thousands of articles out there about buttons and links on the web; the differences and how to use them properly. Hey, I don’t mind. I wrote my own as well¹.

It’s such a common mistake on the web that it’s always worth repeating:

  • Is the intention to send someone to another URL? It’s a link in the form of <a href="">.
  • Is it to trigger some on-page interactivity? It’s a button in the form of <button>.
  • Any devition from from those and you better smurfing know what you are doing.

Eric Eggert wrote a pretty good piece recently with a nice line about why it matters:

If you had a keyboard and your “e” key would only work 90% of the time, it would be infuriating. Reliability and trust in user interfaces is important to allow users to navigate content and application with ease. If you use the right elements, you support users.

Manuel Matuzović has a Button Cheat Sheet that is a lol-inducing ride about why literally everything other than a <button> isn’t as good as a button. Manuel links up Marcy Sutton’s epic The Links vs. Buttons Showdown (video), pitting the two against each other in a mock battle — “We’ll pit two HTML elements against each other in a crusade of better and worse, right and possible wrong. One element is triggered with the space bar, the other with the enter key. Who will win?” I don’t know whether to laugh or cry at how far we have to go to spread this information.

  1. I think our article A Complete Guide to Links and Buttons is a pretty good example of beginner-oriented content, which is my favorite style of content to write and publish! But because there is so much beginner-oriented content on the web, the bar is pretty high if you want to make and impact and get enough SEO for anyone to even ever find it. So, in this case, the idea was to go big and write nearly as much as there is to write about the technical foundation of links and buttons. If you’ve got a knack for this kind of writing, reach out for sure.


The post Buttons vs. Links appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

When a Click is Not Just a Click

The click event is quite simple and easy to use; you listen for the event and run code when the event is fired. It works on just about every HTML element there is, a core feature of the DOM API.

As often the case with the DOM and JavaScript, there are nuances to consider. Some nuances with the click event are typically not much a concern. They are minor and probably most people would never even notice them in the majority of use cases.

Take, for example, the click event listening to the grandfather of interactive elements, the <button> element. There are nuances associated with button clicks and these nuances, like the difference between a “click” from a mouse pointer and “click” from the keyboard. Seen this way, a click is not always a “click” the way it’s typically defined. I actually have run into situations (though not many) where distinguishing between those two types of clicks comes in handy.

How do we distinguish between different types of clicks? That’s what we’re diving into!

First things first

The <button> element, as described by MDN, is simply:

The HTML element represents a clickable button, used to submit forms or anywhere in a document for accessible, standard button functionality. By default, HTML buttons are presented in a style resembling the platform the user agent runs on, but you can change buttons’ appearance with CSS.

The part we’ll cover is obviously the “anywhere in a document for accessible, standard button functionality” part of that description. As you may know, a button element can have native functionality within a form, for example it can submit a form in some situations. We are only really concerning ourselves over the basic clicking function of the element. So consider just a simple button placed on the page for specific functionality when someone interacts with it.

Consider that I said “interacts with it” instead of just clicking it. For historical and usability reasons, one can “click” the button by putting focus on it with tabbing and then using the Space or Enter key on the keyboard. This is a bit of overlap with keyboard navigation and accessibility; this native feature existed way before accessibility was a concern. Yet the legacy feature does help a great deal with accessibility for obvious reasons.

In the example above, you can click the button and its text label will change. After a moment the original text will reset. You can also click somewhere else within the pen, tab to put focus on the button, and then use Space or Enter to “click” it. The same text appears and resets as well. There is no JavaScript to handle the keyboard functionality; it’s a native feature of the browser. Fundamentally, in this example the button is only aware of the click event, but not how it happened.

One interesting difference to consider is the behavior of a button across different browsers, especially the way it is styled. The buttons in these examples are set to shift colors on its active state; so you click it and it turns purple. Consider this image that shows the states when interacting with the keyboard.

Keyboard Interaction States

The first is the static state, the second is when the button has focus from a keyboard tabbing onto it, the third is the keyboard interaction, and the fourth is the result of the interaction. With Firefox you will only see the first two and last states; when interacting with either Enter or Space keys to “click” it you do not see the third state. It stays with the second, or “focused”, state during the interaction and then shifts to the last one. The text changes as expected but the colors do not. Chrome gives us a bit more as you’ll see the first two states the same as Firefox. If you use the Space key to “click” the button you’ll see the third state with the color change and then the last. Interestingly enough, with Chrome if you use Enter to interact with the button you won’t see the third state with the color change, much like Firefox. In case you are curious, Safari behaves the same as Chrome.

The code for the event listener is quite simple:

const button = document.querySelector('#button');

button.addEventListener('click', () => {
  button.innerText = 'Button Clicked!';
  
  window.setTimeout(() => {
    button.innerText = '"click" me';
  }, 2000);
});

Now, let’s consider something here with this code. What if you found yourself in a situation where you wanted to know what caused the “click” to happen? The click event is usually tied to a pointer device, typically the mouse, and yet here the Space or Enter key are triggering the same event. Other form elements have similar functionality depending on context, but any elements that are not interactive by default would require an additional keyboard event to work. The button element doesn’t require this additional event listener.

I won’t go too far into reasons for wanting to know what triggered the click event. I can say that I have occasionally ran into situations where it was helpful to know. Sometimes for styling reasons, sometimes accessibility, and sometimes for specific functionality. Often different context or situations provide for different reasons.

Consider the following not as The Way™ but more of an exploration of these nuances we’re talking about. We’ll explore handling the various ways to interact with a button element, the events generated, and leveraging specific features of these events. Hopefully the following examples can provide some helpful information from the events; or possibly spread out to other HTML elements, as needed.

Which is which?

One simple way to know a keyboard versus mouse click event is leveraging the keyup and mouseup events, taking the click event out of the equation.

Now, when you use the mouse or the keyboard, the changed text reflects which event is which. The keyboard version will even inform you of a Space versus Enter key being used.

Here’s the new code:

const button = document.querySelector('#button');

function reset () {
  window.setTimeout(() => {
    button.innerText = '"click" me';
  }, 2000);
}

button.addEventListener('mouseup', (e) => {
  if (e.button === 0) {
    button.innerText = 'MouseUp Event!';
    reset();
  }
});

button.addEventListener('keyup', (e) => {
  if (e.code === 'Space' || e.code === 'Enter') {
    button.innerText = `KeyUp Event: ${e.code}`;
    reset();
  }
});

A bit verbose, true, but we’ll get to a slight refactor in a bit. This example gets the point across about a nuance that needs to be handled. The mouseup and keyup events have their own features to account for in this situation.

With the mouseup event, about every button on the mouse could trigger this event. We usually wouldn’t want the right mouse button triggering a “click” event on the button, for instance. So we look for the e.button with the value of 0 to identify the primary mouse button. That way it works the same as with the click event yet we know for a fact it was the mouse.

With the keyup event, the same thing happens where about every key on the keyboard will trigger this event. So we look at the event’s code property to wait for the Space or Enter key to be pressed. So now it works the same as the click event but we know the keyboard was used. We even know which of the two keys we’re expecting to work on the button.

Another take to determine which is which

While the previous example works, it seems like a bit too much code for such a simple concept. We really just want to know if the “click” came from a mouse or a keyboard. In most cases we probably wouldn’t care if the source of the click was either the Space or Enter keys. But, if we do care, we can take advantage of the keyup event properties to note which is which.

Buried in the various specifications about the click event (which leads us to the UI Events specification) there are certain properties assigned to the event concerning the mouse location, including properties such as screenX/screenY and clientX/clientY. Some browsers have more, but I want to focus on the screenX/screenY properties for the moment. These two properties essentially give you the X and Y coordinates of the mouse click in relation to the upper-left of the screen. The clientX/clientY properties do the same, but the origin is the upper-left of the browser’s viewport.

This trick relies on the fact that the click event provides these coordinates even though the event was triggered by the keyboard. When a button with the click event is “clicked” by the Space or Enter key it still needs to assign a value to those properties. Since there’s no mouse location to report, if it falls back to zero as the default.

Here’s our new code:

const button = document.querySelector('#button');

button.addEventListener('click', (e) => {
  button.innerText = e.screenX + e.screenY === 0 || e.offsetX + e.offsetY === 0 ?
    'Keyboard Click Event!' : 'Mouse Click Event!';
  
  window.setTimeout(() => {
    button.innerText = '"click" me';
  }, 2000);
});

Back to just the click event, but this time we look for those properties to determine whether this is a keyboard or mouse “click.” We take both screenX and screenY properties, add them together, and see if they equal zero; which makes for an easy test. The possibilities of the button being in the immediate upper-left of the screen to be clicked has to be quite low. It could be possible if one attempted to make such an effort of a pixel-perfect click in such an odd location, but I would think it’s a safe assumption that it won’t happen under normal circumstances.

Now, one might notice the added e.offsetX + e.offsetY === 0 part. I have to explain that bit…

Enter the dreaded browser inconsistencies

While creating and testing this code, the all-too-often problem of cross-browser support reared its ugly head. It turns out that even though most browsers set the screenX and screenY values on a keyboard-caused click event to zero, Safari decides to be different. It applies a proper value to screenX and screenY as if the button was clicked by a mouse. This throws a wrench into my code which is one of the fun aspects of dealing with different browsers — they’re made by different groups of different people creating different outcomes to the same use cases.

But, alas, I needed a solution because I didn’t necessarily want to rely only on the keyup event for this version of the code. I mean, we could if we wanted to, so that’s still an option. It’s just that I liked the idea of treating this as a potential learning exercise to determine what’s happening and how to make adjustments for differences in browsers like we’re seeing here.

Testing what Safari is doing in this case, it appears to be using the offsetX and offsetY properties in the event to determine the location of the “click” and then applying math to determine the screenX and screenY values. That’s a huge over-simplification, but it sort of checks out. The offset properties will be the location of the click based on the upper-left of the button. In this context, Safari applies zero to offsetX and offsetY, which would obviously be seen as the upper-left of the button. From there it treats that location of the button as the determination for the screen properties based on the distance from the upper-left of the button to the upper-left of the screen.

The other usual browsers technically also apply zero to offestX and offsetY, and could be used in place of screenX and screenY. I chose not to go that route. It’s certainly possible to click a button that happens to be at the absolute top-left of the screen is rather difficult while clicking the top-left of a button. Yet, Safari is different so the tests against the screen and offsets is the result. The code, as written, hopes for zeroes on the screen properties and, if they are there, it moves forward assuming a keyboard-caused click. If the screen properties together are larger then zero, it checks the offset properties just in case. We can consider this the Safari check.

This is not ideal, but it wouldn’t be the first time I had to create branching logic due to browser inconsistencies.

In the hope that the behavior of these properties will not change in the future, we have a decent way to determine if a button’s click event happened by mouse or keyboard. Yet technology marches on providing us new features, new requirements, and new challenges to consider. The various devices available to us has started the concept of the “pointer” as a means to interact with elements on the screen. Currently, such a pointer could be a mouse, a pen, or a touch. This creates yet another nuance that we might want to be consider; determining the kind of pointer involved in the click.

Which one out of many?

Now is a good time to talk about Pointer Events. As described by MDN:

Much of today‘s web content assumes the user’s pointing device will be a mouse. However, since many devices support other types of pointing input devices, such as pen/stylus and touch surfaces, extensions to the existing pointing device event models are needed. Pointer events address that need.

So now let’s consider having a need for knowing what type of pointer was involved in clicking that button. Relying on just the click event doesn’t really provide this information. Chrome does have an interesting property in the click event, sourceCapabilities. This property in turn has a property named firesTouchEvents that is a boolean. This information isn’t always available since Firefox and Safari do not support this yet. Yet the pointer event is available much everywhere, even IE11 of all browsers.

This event can provide interesting data about touch or pen events. Things like pressure, contact size, tilt, and more. For our example here we’re just going to focus on pointerType, which tells us the device type that caused the event.

Clicking on the button will now tell you the pointer that was used. The code for this is quite simple:

const button = document.querySelector('#button');

button.addEventListener('pointerup', (e) => {
  button.innerText = `Pointer Event: ${e.pointerType}`;
  
  window.setTimeout(() => {
    button.innerText = '"click" me';
  }, 2000);
});

Really, not that much different than the previous examples. We listen for the pointerup event on the button and output the event’s pointerType. The difference now is there is no event listener for a click event. So tabbing onto the button and using space or enter key does nothing. The click event still fires, but we’re not listening for it. At this point we only have code tied to the button that only responds to the pointer event.

That obviously leaves a gap in functionality, the keyboard interactivity, so we still need to include a click event. Since we’re already using the pointer event for the more traditional mouse click (and other pointer events) we have to lock down the click event. We need to only allow the keyboard itself to trigger the click event.

The code for this is similar to the “Which Is Which” example up above. The difference being we use pointerup instead of mouseup:

const button = document.querySelector('#button');

function reset () {
  window.setTimeout(() => {
    button.innerText = '"click" me';
  }, 2000);
}

button.addEventListener('pointerup', (e) => {
  button.innerText = `Pointer Event: ${e.pointerType}`;
  reset();
});

button.addEventListener('click', (e) => {
  if (e.screenX + e.screenY === 0 || e.offsetX + e.offsetY === 0) {
    button.innerText = 'Keyboard  ||Click Event!';
    reset();
  }
});

Here we’re using the screenX + screenY (with the additional offset check) method to determine if the click was caused by the keyboard. This way a mouse click would be handled by the pointer event. If one wanted to know if the key used was space or enter, then the keyup example above could be used. Even then, the keyup event could be used instead of the click event depending on how you wanted to approach it.

Anoher take to determine which one out of many

In the ever-present need to refactor for cleaner code, we can try a different way to code this.

Yep, works the same as before. Now the code is:

const button = document.querySelector('#button');

function btn_handler (e) {
  if (e.type === 'click' && e.screenX + e.screenY > 0 && e.offsetX + e.offsetY > 0) {
    return false;
  } else if (e.pointerType) {
    button.innerText = `Pointer Event: ${e.pointerType}`;
  } else if (e.screenX + e.screenY === 0) {
    button.innerText = 'Keyboard Click Event!';
  } else {
    button.innerText = 'Something clicked this?';
  }
  
  window.setTimeout(() => {
    button.innerText = '"click" me';
  }, 2000);
}

button.addEventListener('pointerup', btn_handler);
button.addEventListener('click', btn_handler);

Another scaled down version to consider: this time we’ve reduced our code down to a single handler method that both pointerup and click events call. First we detect if the mouse “click” caused the event; if it does, we wish to ignore it in favor of the pointer event. This is checked with a test opposite of the keyboard test; is the sum of screenX and screenY larger than zero? This time there’s an alteration to the offset check by doing the same as the screen test, is the sum of those properties larger than zero as well?

Then the method checks for the pointer event, and upon finding that, it reports which pointer type occurred. Otherwise, the method checks for keyboard interactions and reports accordingly. If neither of those are the culprit, it just reports that something caused this code to run.

So here we have a decent number of examples on how to handle button interactions while reporting the source of those interactions. Yet, this is just one of the handful of form elements that we are so accustomed to using in projects. How does similar code work with other elements?

Checking checkboxes

Indeed, similar code does work very much the same way with checkboxes.

There are a few more nuances, as you might expect by now. The normal usage of <input type="checkbox"> is a related label element that is tied to the input via the for attribute. One major feature of this combination is that clicking on the label element will check the related checkbox.

Now, if we were to attach event listeners for the click event on both elements, we get back what should be obvious results, even if they are a bit strange. For example, we get one click event fired when clicking the checkbox. If we click the label, we get two click events fired instead. If we were to console.log the target of those events, we’ll see on the double event that one is for the label (which makes sense as we clicked it), but there’s a second event from the checkbox. Even though I know these should be the expected results, it is a bit strange because we’re expecting results from user interactions. Yet the results include interactions caused by the browser.

So, the next step is to look at what happens if we were to listen for pointerup, just like some of the previous examples, in the same scenarios. In that case, we don’t get two events when clicking on the label element. This also makes sense as we’re no longer listening for the click event that is being fired from the checkbox when the label is clicked.

There’s yet another scenario to consider. Remember that we have the option to put the checkbox inside the label element, which is common with custom-built checkboxes for styling purposes.

<label for="newsletter">
  <input type="checkbox" />
  Subscribe to my newsletter
</label>

In this case, we really only need to put an event listener on the label and not the checkbox itself. This reduces the number of event listeners involved, and yet we get the same results. Clicks events are fired as a single event for clicking on the label and two events if you click on the checkbox. The pointerup events do the same as before as well, single events if clicking on either element.

These are all things to consider when trying to mimic the behavior of the previous examples with the button element. Thankfully, there’s not too much to it. Here’s an example of seeing what type of interaction was done with a checkbox form element:

This example includes both types of checkbox scenarios mentioned above; the top line is a checkbox/label combination with the for attribute, and the bottom one is a checkbox inside the label. Clicking either one will output a message below them stating which type of interaction happened. So click on one with a mouse or use the keyboard to navigate to them and then interact with Space or Enter; just like the button examples, it should tell you which interaction type causes it.

To make things easier in terms of how many event listeners I needed, I wrapped the checkboxes with a container div that actually responds to the checkbox interactions. You wouldn’t necessarily have to do it this way, but it was a convenient way to do this for my needs. To me, the fun part is that the code from the last button example above just copied over to this example.

const checkbox_container = document.querySelector('#checkbox_container');
const checkbox_msg = document.querySelector('#checkbox_msg');

function chk_handler (e) {
  if (e.type === 'click' && e.screenX + e.screenY > 0 && e.offsetX + e.offsetY > 0) {
    return false;
  } else if (e.pointerType) {
    checkbox_msg.innerText = `Pointer Event: ${e.pointerType}`;
  } else if (e.screenX + e.screenY === 0) {
    checkbox_msg.innerText = 'Keyboard Click Event!';
  } else {
    checkbox_msg.innerText = 'Something clicked this?';
  }
  
  window.setTimeout(() => {
    checkbox_msg.innerText = 'waiting...';
  }, 2000);
}

checkbox_container.addEventListener('pointerup', chk_handler);
checkbox_container.addEventListener('click', chk_handler);

That means we could possibly have the same method being called from the the various elements that need the same detecting the pointer type functionality. Technically, we could put a button inside the checkbox container and it should still work the same. In the end it’s up to you how to implement such things based on the needs of the project.

Radioing your radio buttons

Thankfully, for radio button inputs, we can still use the same code with similar HTML structures. This mostly works the same because checkboxes and radio buttons are essentially created the same way—it’s just that radio buttons tend to come in groups tied together while checkboxes are individuals even in a group. As you’ll see in the following example, it works the same:

Again, same code attached to a similar container div to prevent having to do a number of event listeners for every related element.

When a nuance can be an opportunity

I felt that “nuance” was a good word choice because the things we covered here are not really “issues” with the typical negative connotation that word tends to have in programming circles. I always try to see such things as learning experiences or opportunities. How can I leverage things I know today to push a little further ahead, or maybe it’s time to explore outward into new things to solve problems I face. Hopefully, the examples above provide a somewhat different way to look at things depending on the needs of the project at hand.

We even found an opportunity to explore a browser inconsistency and find a workaround to that situation. Thankfully we don’t run into such things that much with today’s browsers, but I could tell you stories about what we went through when I first started web development.

Despite this article focusing more on form elements because of the click nuance they tend to have with keyboard interactions, some or all of this can be expanded into other elements. It all depends on the context of the situation. For example, I recall having to do multiple events on the same elements depending on the context many times; often for accessibility and keyboard navigation reasons. Have you built a custom <select> element to have a nicer design than the standard one, that also responds to keyboard navigation? You’ll see what I mean when you get there.

Just remember: a “click” today doesn’t always have to be what we think a click has always been.


The post When a Click is Not Just a Click appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Making Disabled Buttons More Inclusive

Let’s talk about disabled buttons. Specifically, let’s get into why we use them and how we can do better than the traditional disabled attribute in HTML (e.g. <button disabled> ) to mark a button as disabled.

There are lots of use cases where a disabled button makes a lot of sense, and we’ll get to those reasons in just a moment. But throughout this article, we’ll be looking at a form that allows us to add a number of tickets to a shopping cart.

This is a good baseline example because there’s a clear situation for disabling the “Add to cart” button: when there are no tickets to add to the cart.

But first, why disabled buttons?

Preventing people from doing an invalid or unavailable action is the most common reason we might reach for a disabled button. In the demo below, we can only add tickets if the number of tickets being added to the cart is greater than zero. Give it a try:

Allow me to skip the code explanation in this demo and focus our attention on what’s important: the “Add to cart” button.

<button type="submit" disabled="disabled">
  Add to cart
</button>

This button is disabled by the disabled attribute. (Note that this is a boolean attribute, which means that it can be written as disabled or disabled="disabled".)

Everything seems fine… so what’s wrong with it?

Well, to be honest, I could end the article right here asking you to not use disabled buttons because they suck, and instead use better patterns. But let’s be realistic: sometimes disabling a button is the solution that makes the most sense.

With that being said, for this demo purpose, we’ll pretend that disabling the “Add to cart” button is the best solution (spoiler alert: it’s not). We can still use it to learn how it works and improve its usability along the way.

Types of interactions

I’d like to clarify what I mean by disabled buttons being usable. You may think, If the button is disabled, it shouldn’t be usable, so… what’s the catch? Bear with me.

On the web, there are multiple ways to interact with a page. Using a mouse is one of the most common, but there are others, like sighted people who use the keyboard to navigate because of a motor impairment.

Try to navigate the demo above using only the Tab key to go forward and Tab + Shift to go backward. You’ll notice how the disabled button is skipped. The focus goes directly from the ticket input to the “dummy terms” link.

Using the Tab key, it changes the focus from the input to the link, skipping the “Add to cart” button.

Let’s pause for a second and recap the reason that lead us to disable the button in the first place versus what we had actually accomplished.

It’s common to associate “interacting” with “clicking” but they are two different concepts. Yes, click is a type of interaction, but it’s only one among others, like hover and focus.

In other words…

All clicks are interactions, but not all interactions are clicks.

Our goal is to prevent the click, but by using disabled, we are preventing not only the click, but also the focus, which means we might be doing as much harm as good. Sure, this behavior might seem harmless, but it causes confusion. People with a cognitive disability may struggle to understand why they are unable to focus the button.

In the following demo, we tweaked the layout a little. If you use a mouse, and hover over the submit button, a tooltip is shown explaining why the button is disabled. That’s great! But if you use only the keyboard, there’s no way of seeing that tooltip because the button cannot be focused with disabled. Ouch!

Using the mouse, the tooltip on the “Add to cart” button is visible on hover. But the tooltip is missing when using the Tab key.

Allow me to once again skip past the code explanation. I highly recommend reading “Inclusive Tooltips” by Haydon Pickering and “Tooltips in the time of WCAG 2.1” by Sarah Higley to fully understand the tooltip pattern.

ARIA to the rescue

The disabled attribute in a <button> is doing more than necessary. This is one of the few cases I know of where a native HTML attribute can do more harm than good. Using an ARIA attribute can do a better job, allowing us to add not only focus on the button, but do so consistently to create an inclusive experience for more people and use cases.

The disabled attribute correlates to aria-disabled="true". Give the following demo a try, again, using only the keyboard. Notice how the button, although marked disabled, is still accessible by focus and triggers the tooltip!

Using the Tab key, the “Add to cart” button is focused and it shows the tooltip.

Cool, huh? Such tiny tweak for a big improvement!

But we’re not done quite yet. The caveat here is that we still need to prevent the click programmatically, using JavaScript.

elForm.addEventListener('submit', function (event) {
  event.preventDefault(); /* prevent native form submit */

  const isDisabled = elButtonSubmit.getAttribute('aria-disabled') === 'true';

  if (isDisabled || isSubmitting) {
    // return early to prevent the ticket from being added
    return;
  }

  isSubmitting = true;
  // ... code to add to cart...
  isSubmitting = false;
})

You might be familiar with this pattern as a way to prevent double clicks from submitting a form twice. If you were using the disabled attribute for that reason, I’d prefer not to do it because that causes the temporary loss of the keyboard focus while the form is submitting.

The difference between disabled and aria-disabled

You might ask: if aria-disabled doesn’t prevent the click by default, what’s the point of using it? To answer that, we need to understand the difference between both attributes:

Feature / Attributedisabledaria-disabled="true"
Prevent click
Prevent hover
Prevent focus
Default CSS styles
Semantics

The only overlap between the two is semantics. Both attributes will announce that the button is indeed disabled, and that’s a good thing.

Contrary to the disabled attribute, aria-disabled is all about semantics. ARIA attributes never change the application behavior or styles by default. Their only purpose is to help assistive technologies (e.g. screen readers) to announce the page content in a more meaningful and robust way.

So far, we’ve talked about two types of people, those who click and those who Tab. Now let’s talk about another type: those with visual impairments (e.g. blindness, low vision) who use screen readers to navigate the web.

People who use screen readers, often prefer to navigate form fields using the Tab key. Now look an how VoiceOver on macOS completely skips the disabled button.

The VoiceOver screen reader skips the “Add to cart” button when using the Tab key.

Once again, this is a very minimal form. In a longer one, looking for a submit button that isn’t there right away can be annoying. Imagine a form where the submit button is hidden and only visible when you completely fill out the form. That’s what some people feel like when the disabled attribute is used.

Fortunately, buttons with disabled are not totally unreachable by screen readers. You can still navigate each element individually, one by one, and eventually you’ll find the button.

The VoiceOver screen reader is able to find and announce the “Add to cart” button.

Although possible, this is an annoying experience. On the other hand, with aria-disabled, the screen reader will focus the button normally and properly announce its status. Note that the announcement is slightly different between screen readers. For example, NVDA and JWAS say “button, unavailable” but VoiceOver says “button, dimmed.”

The VoiceOver screen reader can find the “Add to cart” button using Tab key because of aria-disabled.

I’ve mapped out how both attributes create different user experiences based on the tools we just used:

Tool / Attributedisabledaria-disabled="true"
Mouse or tapPrevents a button click.Requires JS to prevent the click.
TabUnable to focus the button.Able to focus the button.
Screen readerButton is difficult to locate.Button is easily located.

So, the main differences between both attributes are:

  • disabled might skip the button when using the Tab key, leading to confusion.
  • aria-disabled will still focus the button and announce that it exists, but that it isn’t enabled yet; the same way you might perceive it visually.

This is the case where it’s important to acknowledge the subtle difference between accessibility and usability. Accessibility is a measure of someone being able to use something. Usability is a measure of how easy something is to use.

Given that, is disabled accessible? Yes. Does it have a good usability? I don’t think so.

Can we do better?

I wouldn’t feel good with myself if I finished this article without showing you the real inclusive solution for our ticket form example. Whenever possible, don’t use disabled buttons. Let people click it at any time and, if necessary, show an error message as feedback. This approach also solves other problems:

  • Less cognitive friction: Allow people to submit the form at any time. This removes the uncertainty of whether the button is even disabled in the first place.
  • Color contrast: Although a disabled button doesn’t need to meet the WCAG 1.4.3 color contrast, I believe we should guarantee that an element is always properly visible regardless of its state. But that’s something we don’t have to worry about now because the button isn’t disabled anymore.

Final thoughts

The disabled attribute in <button> is a peculiar case where, although highly known by the community, it might not be the best approach to solve a particular problem. Don’t get me wrong because I’m not saying disabled is always bad. There are still some cases where it still makes sense to use it (e.g. pagination).

To be honest, I don’t see the disabled attribute exactly as an accessibility issue. What concerns me is more of a usability issue. By swapping the disabled attribute with aria-disabled, we can make someone’s experience much more enjoyable.

This is yet one more step into my journey on web accessibility. Over the years, I’ve discovered that accessibility is much more than complying with web standards. Dealing with user experiences is tricky and most situations require making trade-offs and compromises in how we approach a solution. There’s no silver bullet for perfect accessibility.

Our duty as web creators is to look for and understand the different solutions that are available. Only then we can make the best possible choice. There’s no sense in pretending the problems don’t exist.

At the end of the day, remember that there’s nothing preventing you from making the web a more inclusive place.

Bonus

Still there? Let me mention two last things about this demo that I think are worthy:

1. Live Regions will announce dynamic content

In the demo, two parts of the content changed dynamically: the form submit button and the success confirmation (“Added [X] tickets!”).

These changes are visually perceived, however, for people with vision impairments using screen readers, that just ain’t the reality. To solve it, we need to turn those messages into Live Regions. Those allow assistive technologies to listen for changes and announce the updated messages when they happen.

There is a .sr-only class in the demo that hides a <span> containing a loading message, but allows it to be announced by screen readers. In this case, aria-live="assertive" is applied to the <span> and it holds a meaningful message after the form is submitting and is in the process of loading. This way, we can announce to the user that the form is working and to be patient as it loads. Additionally, we do the same to the form feedback element.

<button type="submit" aria-disabled="true">
  Add to cart
  <span aria-live="assertive" class="sr-only js-loadingMsg">
     <!-- Use JavaScript to inject the the loading message -->
  </span>
</button>

<p aria-live="assertive" class="formStatus">
  <!-- Use JavaScript to inject the success message -->
</p>

Note that the aria-live attribute must be present in the DOM right from the beginning, even if the element doesn’t hold any message yet, otherwise, Assistive Technologies may not work properly.

Form submit feedback message being announced by the screen reader.

There’s much more to tell you about this little aria-live attribute and the big things it does. There are gotchas as well. For example, if it is applied incorrectly, the attribute can do more harm than good. It’s worth reading “Using aria-live” by Ire Aderinokun and Adrian Roselli’s “Loading Skeletons” to better understand how it works and how to use it.

2. Do not use pointer-events to prevent the click

This is an alternative (and incorrect) implementation that I’ve seen around the web. This uses pointer-events: none; in CSS to prevent the click (without any HTML attribute). Please, do not do this. Here’s an ugly Pen that will hopefully demonstrate why. I repeat, do not do this.

Although that CSS does indeed prevent a mouse click, remember that it won’t prevent focus and keyboard navigation, which can lead to unexpected outcomes or, even worse, bugs.

In other words, using this CSS rule as a strategy to prevent a click, is pointless (get it?). ;)


The post Making Disabled Buttons More Inclusive appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Gutenberg 9.4 Introduces Button Width Selector and Typography Controls for List Block

Gutenberg 9.4.0 was released this week with many small improvements to existing features, while work on full site editing continues. This release will not be included in the upcoming WordPress 5.6 release but those who are using the Gutenberg plugin will have access to the improvements right away.

The button block now has a width selector, which allows the user to set the button to 25%, 50%, 75%, or 100% of the parent container. By default, a button’s width is determined by the size of its content. If you like bigger buttons, this update will give you more flexibility. Button margins are also included in the width calculations, so users can create multiple buttons in a row, or a grid of buttons, and have them properly fit together and aligned.

Making a button is easier than it has ever been before. Gone are the days of using shortcodes or hunting for the correct CSS class to apply in order to match the theme. Button creation used to be so needlessly difficult with a fragmented, unfriendly workflow, but the block editor continues to chip away at the complexity with each new release.

Version 9.4 also introduces typography controls for the list block. Gutenberg contributors have been discussing adding color and text size customizations to all text-based blocks since 2018, and the list block is finally getting some font size controls.

Social icons can also be resized now. Users can select from several preset sizes, including small, normal, large, and huge.

The 9.4 update adds support for <kbd> tags with a new button in the overflow rich text menu. These tags are useful for displaying content in the browser’s default monospace font, which helps when writing documentation or articles with inline code.

This release lays the groundwork for handling block variation transformations. Block variations are essentially the same block with registered variations that appear as a separate block in the block inserter. For example, the navigation block has horizontal and vertical variations. The editor now introduces a transform option for the scope field in block variations, so developers can control how to handle these transformations.

Enhancements in this release add polish to many aspects of the UI, including the inserter search, custom select menu styles, the link interface, Search block styling, shortcode block styling, and reduces the UI on hover (an optional setting in preferences).

One handy new feature for writing is that users can now add a header by typing /h1 to /h6 followed by enter/return. While I like the idea of this, it seems unintuitive to have to use enter/return to change the block to a header. This feature would be easier to remember if it mimicked the existing feature that allows users to add a header by typing ### followed by a space. Changing the trigger action to a space instead of a return would make more sense here.

Version 9.4 also includes a great deal of progress behind the scenes on experiments, including the full site editing framework, FSE blocks, the site editor, and global styles. Check out the changelog for a full list of bug fixes and enhancements.

How to Recreate the Ripple Effect of Material Design Buttons

When I first discovered Material Design, I was particularly inspired by its button component. It uses a ripple effect to give users feedback in a simple, elegant way.

How does this effect work? Material Design’s buttons don’t just sport a neat ripple animation, but the animation also changes position depending on where each button is clicked.

We can achieve the same result. We’ll start with a concise solution using ES6+ JavaScript, before looking at a few alternative approaches.

HTML

Our goal is to avoid any extraneous HTML markup. So we’ll go with the bare minimum:

<button>Find out more</button>

Styling the button

We’ll need to style a few elements of our ripple dynamically, using JavaScript. But everything else can be done in CSS. For our buttons, it’s only necessary to include two properties.

button {
  position: relative;
  overflow: hidden;
}

Using position: relative allows us to use position: absolute on our ripple element, which we need to control its position. Meanwhile, overflow: hidden prevents the ripple from exceeding the button’s edges. Everything else is optional. But right now, our button is looking a bit old school. Here’s a more modern starting point:

/* Roboto is Material's default font */
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

button {
  position: relative;
  overflow: hidden;
  transition: background 400ms;
  color: #fff;
  background-color: #6200ee;
  padding: 1rem 2rem;
  font-family: 'Roboto', sans-serif;
  font-size: 1.5rem;
  outline: 0;
  border: 0;
  border-radius: 0.25rem;
  box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

Styling the ripples

Later on, we’ll be using JavaScript to inject ripples into our HTML as spans with a .ripple class. But before turning to JavaScript, let’s define a style for those ripples in CSS so we have them at the ready:

span.ripple {
  position: absolute; /* The absolute position we mentioned earlier */
  border-radius: 50%;
  transform: scale(0);
  animation: ripple 600ms linear;
  background-color: rgba(255, 255, 255, 0.7);
}

To make our ripples circular, we’ve set the border-radius to 50%. And to ensure each ripple emerges from nothing, we’ve set the the default scale to 0. Right now, we won’t be able to see anything because we don’t yet have a value for the top, left, width, or height properties; we’ll soon be injecting these properties with JavaScript.

As for our CSS, the last thing we need to add is an end state for the animation:

@keyframes ripple {
  to {
    transform: scale(4);
    opacity: 0;
  }
}

Notice that we’re not defining a starting state with the from keyword in the keyframes? We can omit from and CSS will construct the missing values based on those that apply to the animated element. This occurs if the relevant values are stated explicitly — as in transform: scale(0) — or if they’re the default, like opacity: 1.

Now for the JavaScript

Finally, we need JavaScript to dynamically set the position and size of our ripples. The size should be based on the size of the button, while the position should be based on both the position of the button and of the cursor.

We’ll start with an empty function that takes a click event as its argument:

function createRipple(event) {
  //
}

We’ll access our button by finding the currentTarget of the event.

const button = event.currentTarget;

Next, we’ll instantiate our span element, and calculate its diameter and radius based on the width and height of the button.

const circle = document.createElement("span");
const diameter = Math.max(button.clientWidth, button.clientHeight);
const radius = diameter / 2;

We can now define the remaining properties we need for our ripples: the left, top, width and height.

circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (button.offsetLeft + radius)}px`;
circle.style.top = `${event.clientY - (button.offsetTop + radius)}px`;
circle.classList.add("ripple"); 

Before adding our span element to the DOM, it’s good practice to check for any existing ripples that might be leftover from previous clicks, and remove them before executing the next one.

const ripple = button.getElementsByClassName("ripple")[0];

if (ripple) {
  ripple.remove();
}

As a final step, we append the span as a child to the button element so it is injected inside the button.

button.appendChild(circle);

With our function complete, all that’s left is to call it. This could be done in a number of ways. If we want to add the ripple to every button on our page, we can use something like this:

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  button.addEventListener("click", createRipple);
}

We now have a working ripple effect!

Taking it further

What if we want to go further and combine this effect with other changes to our button’s position or size? The ability to customize is, after all, one of the main advantages we have by choosing to recreate the effect ourselves. To test how easy it is to extend our function, I decided to add a “magnet” effect, which causes our button to move towards our cursor when the cursor’s within a certain area.

We need to rely on some of the same variables defined in the ripple function. Rather than repeating code unnecessarily, we should store them somewhere they’re accessible to both methods. But we should also keep the shared variables scoped to each individual button. One way to achieve this is by using classes, as in the example below:

Since the magnet effect needs to keep track of the cursor every time it moves, we no longer need to calculate the cursor position to create a ripple. Instead, we can rely on cursorX and cursorY.

Two important new variables are magneticPullX and magneticPullY. They control how strongly our magnet method pulls the button after the cursor. So, when we define the center of our ripple, we need to adjust for both the position of the new button (x and y) and the magnetic pull.

const offsetLeft = this.left + this.x * this.magneticPullX;
const offsetTop = this.top + this.y * this.magneticPullY;

To apply these combined effects to all our buttons, we need to instantiate a new instance of the class for each one:

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  new Button(button);
}

Other techniques

Of course, this is only one way to achieve a ripple effect. On CodePen, there are lots of examples that show different implementations. Below are some of my favourites.

CSS-only

If a user has disabled JavaScript, our ripple effect doesn’t have any fallbacks. But it’s possible to get close to the original effect with just CSS, using the :active pseudo-class to respond to clicks. The main limitation is that the ripple can only emerge from one spot — usually the center of the button — rather than responding to the position of our clicks. This example by Ben Szabo is particularly concise:

Pre-ES6 JavaScript

Leandro Parice’s demo is similar to our implementation but it’s compatible with earlier versions of JavaScript: 

jQuery 

This example use jQuery to achieve the ripple effect. If you already have jQuery as a dependency, it could help save you a few lines of code. 

React

Finally, one last example from me. Although it’s possible to use React features like state and refs to help create the ripple effect, these aren’t strictly necessary. The position and size of the ripple both need to be calculated for every click, so there’s no advantage to holding that information in state. Plus, we can access our button element from the click event, so we don’t need refs either.

This React example uses a createRipple function identical to that of this article’s first implementation. The main difference is that — as a method of the Button component — our function is scoped to that component. Also, the onClick event listener is now part of our JSX:


The post How to Recreate the Ripple Effect of Material Design Buttons appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Using a brightness() filter to generically highlight content

Rick Strahl:

I can’t tell you how many times over the years I’ve implemented a custom ‘button’ like CSS implementation. Over the years I’ve used images, backgrounds, gradients, and opacity to effectively ‘highlight’ a control. All that works of course, but the problem with most of these approaches is that one way or the other you’re hard coding a color value, image, or gradient.

You certainly have a lot more control if you specify exact colors, but if you can pull off brightening, darkening, or even a hue-shift in a way that feels cohesive on your site, it’s certainly a lot less code to maintain,

.button.specific-button {
  background: #4CAF50;
}
.button.specific-button:focus,
.button.specific-button:hover {
  background: #A5D6A7;
}

/* vs. */
.button:focus,
.button:hover {
  filter: brightness(120%);
}

/* or maybe you're super hardcore and do it everywhere */
:focus,
:hover {
  filter: brightness(120%) saturate(120%);
}

Direct Link to ArticlePermalink


The post Using a brightness() filter to generically highlight content appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

20 Amazing Pure CSS Animated Buttons

If you want to give your website a little extra flair, you’ll definitely want to investigate and utilize CSS animated buttons. These bits of code add a layer of interactivity to your website that most site visitors will appreciate. Plus, they can be used to add a sense of dynamics and further help to solidify your brand.

If you’re not sure where to start in getting these buttons for your site, however, we’ve taken the guesswork out of the process for you. What follows is a list of 20 different CSS animated buttons that you can add to your site via some relatively simple CSS. It doesn’t get much easier!

The UX Designer Toolbox

Unlimited Downloads: 500,000+ Wireframe & UX Templates, UI Kits & Design Assets
Starting at only $16.50 per month!


1. Stylish Animated CSS Buttons for Bloggers

See the Pen Stylish Animated CSS Buttons For Blogger. by Prio-Soft™ (@priosoft) on CodePen.default

 

This set of stylish animated CSS buttons are ideal for use by bloggers. They offer a wide range of hover effects from swiping color across a button from left to right (and vice versa), from top to bottom, that highlights the outline of the button, and more.

2. Animated CSS Buttons

See the Pen animated-css-buttons by Naved khan (@Navedkhan012) on CodePen.default

 

This set of animated CSS buttons have a simplicity to them that makes them highly usable in a wide variety of contexts. On hover, these buttons fill with color at angles, employ swipe effects, pattern fills, and more.

3. More Animated CSS Buttons

See the Pen Animated CSS Buttons by an (@annguyn) on CodePen.default

 

This set of CSS buttons are very simple but that’s precisely what makes them appeal. If you want to add just a tad of interactivity to your site, these are a safe bet.

4. CSS3 Buttons

See the Pen css 3 buttons by Oleg Semenov (@wemonsh) on CodePen.default

 

Now these CSS3 buttons offer cool transition effects. Some fill with color upon hover but others develop a drop shadow effect that makes the buttons appear to lift off the screen.

5. Simple CSS Buttons Animation

See the Pen Simple CSS buttons animation by Michael Domanych (@mhouse) on CodePen.default

 

As the title of this set of buttons would suggest, these CSS buttons are simple and straightforward in their design. They offer slide-in color from various directions as well as filling from the center out.

6. CSS + SVG Button Animation

See the Pen CSS + SVG Button Animation by Clément (@clmntclmnt) on CodePen.default

 

Here’s a single animated button but its effect is undeniably compelling. Upon hover, this button fills with color from the sides to the middle then a contrasting color outline appears around the button.

7. Animation with Cubic Bezier

See the Pen animation with cubic bezier by Franca (@franca_) on CodePen.default

 

This fun button would add real style to any website. When you hover over the button, the text within it changes color.

8. Pure CSS Button

See the Pen Pure CSS Button (animation with clip-path) by Marco Antônio (@thismarcoantonio) on CodePen.default

 

This button is a bit different than the rest of this list. It has a clip path that makes it so when you hover over the button text, a circle animation slides across an arrow, transforming the pointed end into a dot.

9. Blobs Button

See the Pen Blobs button by Hilary (@hilwat) on CodePen.default

 

As its name would suggest, the Blobs Button fills via colorful blobs upon hover. This is a great choice for those looking to add a touch of whimsy or fun to their websites.

10. Simple CSS Button Hover Effects

See the Pen Simple CSS Button Hover Effects by Natalia  Reshetnikova (@natalia-reshetnikova) on CodePen.default

 

Here’s another set of rather understated animated buttons that still manage to make a real impact. Some of the effects include the button’s text spreading out, The button itself splitting into an X shape, and color shifts.

11. CSS Button with Hover Effect

See the Pen CSS Button With Hover Effect by Raj Kamal Chenumalla (@avvign) on CodePen.default

 

Here’s another button that offers a super straightforward design. Upon hover, it develops an aura that quickly disappears. Subtle, yet effective.

12. 100 Days CSS Button N 045

See the Pen 100 days css Button N° 045 by Vitor Siqueira (@vitor-siqueira) on CodePen.default

 

This simple button has an effect where the outline of the button intensifies in color and chases its border when you hover over it.

13. Pure CSS Buttons

See the Pen Pure CSS Buttons by Ishaan Saxena (@ishaansaxena) on CodePen.default

 

Here’s another set of super simple CSS buttons. They fill with color from all directions upon hover and can be used as icons as well.

14. Auto Width CSS Button Flip

See the Pen Auto Width  Css Button Flip by Alex Moore (@MoorLex) on CodePen.default

 

What a fun option! When you hover over this animated button it appears to tip forward, revealing different text on the “back” of the button.

15. Collection of Button Hover Effects

See the Pen Collection of Button Hover Effects by David Conner (@davidicus) on CodePen.default

 

Here’s another set of animated CSS buttons that use fun hover effects to make a statement. Outline effects, fills, and color shifts make up the majority of the effects used here.

16. Pure CSS Button with Ring Indicator

See the Pen Pure CSS Button with Ring Indicator by Cole McCombs (@mccombsc) on CodePen.default

 

If you’re wanting to draw attention to a call-to-action or something like that, this button might be the perfect choice. It constantly emanates a ring out from its center, drawing the eye to it. Then, upon hover, the button is highlighted and lifts slightly.

17. CSS3 Button Hover Effects with FontAwesome

See the Pen CSS3 Button Hover Effects with FontAwesome by foxeisen (@foxeisen) on CodePen.default

 

This set of buttons use hover effects in conjunction with FontAwesome for some timeless design options. Hovering over these buttons reveal an arrow instead of text, a textual shift to accommodate an arrow on the button, and more.

18. CSS3 3D Flip Button

See the Pen CSS3 3d flip button by Sean Michael (@seansean11) on CodePen.default

 

Unlike all the other buttons on this list, the CSS3 3D Flip Button displays an effect when you click on it. Once you click, the button rolls up to reveal new text and icons. This is great way to indicate a form has been submitted, for instance.

19. Button Fun

See the Pen Button Fun by Jack Cuthbert (@JackCuthbert) on CodePen.default

 

Here’s another great button option that would appeal to those looking for a more understated look. When you hover over these buttons, the text and outline changes color with a cool aura effect.

20. Button Shine Effect

See the Pen Button Shine Effect by Dan Mensinger (@dmensinger) on CodePen.default

 

The last animated CSS button on our list is this Button Shine Effect. Upon hover, the button changes color and appears to shine as though a light has been passed over its surface. It’s simple and effective, the perfect level of interactivity to spark interest in your website.

Give These CSS Animated Buttons a Try

So, what have we learned here? You can add interactivity to your website without being an expert developer. And this collection of CSS animated buttons make it easy to add a little something extra to your site’s design. Whether you want to punch up a call-to-action or make your navigation more fun, give these buttons a try and see what works best for your site. For more CSS button tips and tutorials, check out our other articles here.

A Complete Guide to Links and Buttons

There is a lot to know about links and buttons in HTML. There is markup implementation and related attributes, styling best practices, things to avoid, and the even-more-nuanced cousins of the link: buttons and button-like inputs.

Let's take a look at the whole world of links and buttons, and all the considerations at the HTML, CSS, JavaScript, design, and accessibility layers that come with them. There are plenty of pitfalls and bad practices to avoid along the way. By covering it, we’ll have a complete good UX implementation of both elements.

Quick guidelines on when to use each:

  • Are you giving a user a way to go to another page or a different part of the same page? Use a link (<a href="/somewhere">link</a>)
  • Are you making a JavaScript-powered clickable action? Use a button (<button type="button">button</button>)
  • Are you submitting a form? Use a submit input (<input type="submit" value="Submit">)

Links

Links are one of the most basic, yet deeply fundamental and foundational building blocks of the web. Click a link, and you move to another page or are moved to another place within the same page.

Table of Contents

A basic link

<a href="https://css-tricks.com">CSS-Tricks</a>

That's a link to a "fully qualified" or "absolute" URL.

A relative link

You can link "relatively" as well:

<!-- Useful in navigation, but be careful in content that may travel elsewhere (e.g. RSS) -->
<a href="/pages/about.html">About</a>

That can be useful, for example, in development where the domain name is likely to be different than the production site, but you still want to be able to click links. Relative URLs are most useful for things like navigation, but be careful of using them within content — like blog posts — where that content may be read off-site, like in an app or RSS feed.

A jump link

Links can also be "hash links" or "jump links" by starting with a #:

<a href="#section-2">Section Two</a>
<!-- will jump to... -->
<section id="section-2"></section>

Clicking that link will "jump" (scroll) to the first element in the DOM with an ID that matches, like the section element above.

💥 Little trick: Using a hash link (e.g. #0) in development can be useful so you can click the link without being sent back to the top of the page like a click on a # link does. But careful, links that don't link anywhere should never make it to production.

💥 Little trick: Jump-links can sometimes benefit from smooth scrolling to help people understand that the page is moving from one place to another.

It's a fairly common UI/UX thing to see a "Back to top" link on sites, particularly where important navigational controls are at the top but there is quite a bit of content to scroll (or otherwise navigate) through. To create a jump link, link to the ID of an element that is at the top of the page where it makes sense to send focus back to.

<a href="#top-of-page">Back to Top</a>

Jump links are sometimes also used to link to other anchor (<a>) elements that have no href attribute. Those are called "placeholder" links:

<a id="section-2"></a>
<h3>Section 2</h3>

There are accessibility considerations of these, but overall they are acceptable.

Disabled links

A link without an href attribute is the only practical way to disable a link. Why disable a link? Perhaps it's a link that only becomes active after logging in or signing up.

a:not[href] {
  /* style a "disabled" link */
}

When a link has no href, it has no role, no focusability, and no keyboard events. This is intentional. You could think of it like a <span>.

Do you need the link to open in a new window or tab?

You can use the target attribute for that, but it is strongly discouraged.

<a href="https://css-tricks.com" target="_blank" rel="noopener noreferrer">
  CSS-Tricks
</a>

The bit that makes it work is target="_blank", but note the extra rel attribute and values there which make it safer and faster.

Making links open in new tabs is a major UX discussion. We have a whole article about when to use it here. Summarized:

Don't use it:

  • Because you or your client prefer it personally.
  • Because you're trying to beef up your time on site metric.
  • Because you're distinguishing between internal and external links or content types.
  • Because it's your way out of dealing with infinite scroll trickiness.

Do use it:

  • Because a user is doing something on the current page, like actively playing media or has unsaved work.
  • You have some obscure technical reason where you are forced to (even then you're still probably the rule, not the exception).

Need the link to trigger a download?

The download attribute on a link will instruct the browser to download the linked file rather than opening it within the current page/tab. It's a nice UX touch.

<a href="/files/file.pdf" download>Download PDF</a>

The rel attribute

This attribute is for the relationship of the link to the target.

The rel attribute is also commonly used on the <link> element (which is not used for creating hyperlinks, but for things like including CSS and preloading). We're not including rel values for the <link> element here, just anchor links.

Here are some basic examples:

<a href="/page/3" rel="next">Next</a>
<a href="/page/1" rel="prev">Previous</a>

<a href="http://creativecommons.org/licenses/by/2.0/" rel="license">cc by 2.0</a>

<a href="/topics/" rel="directory">All Topics</a>
  • rel="alternate": Alternate version of the document.
  • rel="author": Author of the document.
  • rel="help": A resource for help with the document.
  • rel="license": License and legal information.
  • rel="manifest": Web App Manifest document.
  • rel="next": Next document in the series.
  • rel="prev": Previous document in the series.
  • rel="search": A document meant to perform a search in the current document.

There are also some rel attributes specifically to inform search engines:

  • rel="sponsored": Mark links that are advertisements or paid placements (commonly called paid links) as sponsored.
  • rel="ugc": For not-particularly-trusted user-generated content, like comments and forum posts.
  • rel="nofollow": Tell the search engine to ignore this and not associate this site with where this links to.

And also some rel attributes that are most security-focused:

  • rel="noopener": Prevent a new tab from using the JavaScript window.opener feature, which could potentially access the page containing the link (your site) to perform malicious things, like stealing information or sharing infected code. Using this with target="_blank" is often a good idea.
  • rel="noreferrer": Prevent other sites or tracking services (e.g. Google Analytics) from identifying your page as the source of clicked link.

You can use multiple space-separated values if you need to (e.g. rel="noopener noreferrer")

And finally, some rel attributes come from the microformats standard, like:

  • rel="directory": Indicates that the destination of the hyperlink is a directory listing containing an entry for the current page.
  • rel="tag": Indicates that the destination of that hyperlink is an author-designated "tag" (or keyword/subject) for the current page.
  • rel="payment": Indicates that the destination of that hyperlink provides a way to show or give support for the current page.
  • rel="help": States that the resource linked to is a help file or FAQ for the current document.

ARIA roles

The default role of a link is link, so you do not need to do:

<a role="link" href="/">Link</a>

You'd only need that if you were faking a link, which would be a weird/rare thing to ever need to do, and you'd have to use some JavaScript in addition to this to make it actually follow the link.

<span class="link" tabindex="0" role="link" data-href="/">
  Fake accessible link created using a span
</span>

Just looking above you can see how much extra work faking a link is, and that is before you consider that is breaks right-clicking, doesn't allow opening in a new tab, doesn't work with Windows High Contrast Mode and other reader modes and assistive technology. Pretty bad!

A useful ARIA role to indicate the current page, like:

<a href="/" aria-current="page">Home</a>
<a href="/contact">Contact</a>
<a href="/about">About/a></a>

Should you use the title attribute?

Probably not. Save this for giving an iframe a short, descriptive title.

<a title="I don't need to be here" href="/">
  List of Concerts
</a>

title provides a hover-triggered UI popup showing the text you wrote. You can't style it, and it's not really that accessible.

Hover-triggered is the key phrase here. It's unusable on any touch-only device. If a link needs more contextual information, provide that in actual content around the link, or use descriptive text the link itself (as opposed to something like "Click Here").

Icon-only links

If a link only has an icon inside it, like:

<a href="/">😃</a>

<a href="/">
  <svg> ... </svg>
</a>

That isn't enough contextual information about the link, particularly for accessibility reasons, but potentially for anybody. Links with text are almost always more clear. If you absolutely can't use text, you can use a pattern like:

<a href="/">
  <!-- Hide the icon from assistive technology -->
  <svg aria-hidden="true" focusable="false"> ... </svg>
  <!-- Acts as a label that is hidden from view -->
  <span class="visually-hidden">Useful link text</span>
</a>

visually-hidden is a class used to visually hide the label text with CSS:

.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

Unlike aria-label, visually hidden text can be translated and will hold up better in specialized browsing modes.

Links around images

Images can be links if you wrap them in a link. There is no need to use the alt text to say the image is a link, as assistive technology will do that already.

<a href="/buy/puppies/now">
  <img src="puppy.jpg" alt="A happy puppy.">
</a>

Links around bigger chunks of content

You can link a whole area of content, like:

<a href="/article/">
  <div class="card">
    <h2>Card</h2>
    <img src="..." alt="...">
    <p>Content</p>
  </div>
</a>

But it's slightly weird, so consider the UX of it if you ever do it. For example, it can be harder to select the text, and the entire element needs styling to create clear focus and hover states.

Take this example, where the entire element is wrapped in a link, but there are no hover and focus states applied to it.

If you need a link within that card element, well, you can't nest links. You could get a little tricky if you needed ot, like using a pseudo-element on the link which is absolutely positioned to cover the whole area.

Additionally, this approach can make really long and potentially confusing announcements for screen readers. Even though links around chunks of content is technically possible, it's best to avoid doing this if you can.

Here's the default look of a link:

The default User-Agent styling of a link.

It's likely you'll be changing the style of your links, and also likely you'll use CSS to do it. I could make all my links red in CSS by doing:

a {
  color: red;
}

Sometimes selecting and styling all links on a page is a bit heavy-handed, as links in navigation might be treated entirely differently than links within text. You can always scope selectors to target links within particular areas like:

/* Navigation links */
nav a { }

/* Links in an article */
article a { }

/* Links contained in an element with a "text" class */
.text a { }

Or select the link directly to style.

.link {
  /* For styling <a class="link" href="/"> */
}

a[aria-current="page"] {
  /* You'll need to apply this attribute yourself, but it's a great pattern to use for active navigation. */
}

Link states

Links are focusable elements. In other words, they can be selected using the Tab key on a keyboard. Links are perhaps the most common element where you'll very consciously design the different states, including a :focus state.

  • :hover: For styling when a mouse pointer is over the link.
  • :visited: For styling when the link has been followed, as best as the browser can remember. It has limited styling ability due to security.
  • :link: For styling when a link has not been visited.
  • :active: For styling when the link is pressed (e.g. the mouse button is down or the element is being tapped on a touch screen).
  • :focus: Very important! Links should always have a focus style. If you choose to remove the default blue outline that most browsers apply, also use this selector to re-apply a visually obvious focus style.

These are chainable like any pseudo-class, so you could do something like this if it is useful for your design/UX.

/* Style focus and hover states in a single ruleset */
a:focus:hover { }

You can style a link to look button-like

Perhaps some of the confusion between links and buttons is stuff like this:

Very cool "button" style from Katherine Kato.

That certainly looks like a button! Everyone would call that a button. Even a design system would likely call that a button and perhaps have a class like .button { }. But! A thing you can click that says "Learn More" is very much a link, not a button. That's completely fine, it's just yet another reminder to use the semantically and functionally correct element.

Color contrast

Since we often style links with a distinct color, it's important to use a color with sufficient color contrast for accessibility. There is a wide variety of visual impairments (see the tool WhoCanUse for simulating color combinations with different impairments) and high contrast helps nearly all of them.

Perhaps you set a blue color for links:

The blue link is #2196F3.

While that might look OK to you, it's better to use tools for testing to ensure the color has a strong enough ratio according to researched guidelines. Here, I'll look at Chrome DevTools and it will tell me this color is not compliant in that it doesn't have enough contrast with the background color behind it.

Chrome DevTools is telling us this link color does not have enough contrast.

Color contrast is a big consideration with links, not just because they are often colored in a unique color that needs to be checked, but because they have all those different states (hover, focus, active, visited) which also might have different colors. Compound that with the fact that text can be selected and you've got a lot of places to consider contrast. Here's an article about all that.

Styling "types" of links

We can get clever in CSS with attribute selectors and figure out what kind of resource a link is pointing to, assuming the href value has useful stuff in it.

/* Style all links that include .pdf at the end */
a[href$=".pdf"]::after {
  content: " (PDF)";
}

/* Style links that point to Google */
a[href*="google.com"] {
  color: purple;
}

Styling links for print

CSS has an "at-rule" for declaring styles that only take effect on printed media (e.g. printing out a web page). You can include them in any CSS like this:

@media print {
  /* For links in content, visually display the link */ 
  article a::after { 
    content: " (" attr(href) ")";
  }
}

Resetting styles

If you needed to take all the styling off a link (or really any other element for that matter), CSS provides a way to remove all the styles using the all property.

.special-area a {
  all: unset;
  all: revert;
  
  /* Start from scratch */
  color: purple;
}

You can also remove individual styles with keywords. (Again, this isn't really unique to links, but is generically useful):

a {
  /* Grab color from nearest parent that sets it */
  color: inherit;

  /* Wipe out style (turn black) */
  color: initial;

  /* Change back to User Agent style (blue) */
  color: revert;
}

Say you wanted to stop the clicking of a link from doing what it normally does: go to that link or jump around the page. In JavaScript, you can usepreventDefault to prevent jumping around.

const jumpLinks = document.querySelectorAll("a[href^='#']");

jumpLinks.forEach(link => {
 link.addEventListener('click', event => {
    event.preventDefault();
    // Do something else instead, like handle the navigation behavior yourself
  });
});

This kind of thing is at the core of how "Single Page Apps" (SPAs) work. They intercept the clicks so browsers don't take over and handle the navigation.

SPAs see where you are trying to go (within your own site), load the data they need, replace what they need to on the page, and update the URL. It's an awful lot of work to replicate what the browser does for free, but you get the ability to do things like animate between pages.

Another JavaScript concern with links is that, when a link to another page is clicked, the page is left and another page loads. That can be problematic for something like a page that contains a form the user is filling out but hasn't completed. If they click the link and leave the page, they lose their work! Your only opportunity to prevent the user from leaving is by using the beforeunload event.

window.addEventListener("beforeunload", function(event) {
  // Remind user to save their work or whatever.
});

A link that has had its default behavior removed won't announce the new destination. This means a person using assistive technology may not know where they wound up. You'll have to do things like update the page's title and move focus back up to the top of the document.

JavaScript frameworks

In a JavaScript framework, like React, you might sometimes see links created from something like a <Link /> component rather than a native <a> element. The custom component probably creates a native <a> element, but with extra functionality, like enabling the JavaScript router to work, and adding attributes like aria-current="page" as needed, which is a good thing!

Ultimately, a link is a link. A JavaScript framework might offer or encourage some level of abstraction, but you're always free to use regular links.

We covered some accessibility in the sections above (it’s all related!), but here are some more things to think about.

  • You don’t need text like “Link” or “Go to” in the link text itself. Make the text meaningful (“documentation” instead of “click here”).
  • Links already have an ARIA role by default (role="link") so there's no need to explicitly set it.
  • Try not to use the URL itself as the text (<a href="google.com">google.com</a>)
  • Links are generally blue and generally underlined and that's generally good.
  • All images in content should have alt text anyway, but doubly so when the image is wrapped in a link with otherwise no text.

Unique accessible names

Some assistive technology can create lists of interactive elements on the page. Imagine a group of four article cards that all have a "Read More", the list of interactive elements will be like:

  • Read More
  • Read More
  • Read More
  • Read More

Not very useful. You could make use of that .visually-hidden class we covered to make the links more like:

<a href="/article">
  Read More
  <span class="visually-hidden">
    of the article "Dancing with Rabbits".
  <span>
</a>

Now each link is unique and clear. If the design can support it, do it without the visually hidden class to remove the ambiguity for everyone.

Buttons

Buttons are for triggering actions. When do you use the <button> element? A good rule is to use a button when there is “no meaningful href.” Here’s another way to think of that: if clicking it doesn’t do anything without JavaScript, it should be a <button>.

A <button> that is within a <form>, by default, will submit that form. But aside from that, button elements don't have any default behavior, and you'll be wiring up that interactivity with JavaScript.

Table of Contents

HTML implementation

<button>Buy Now</button>

Buttons inside of a <form> do something by default: they submit the form! They can also reset it, like their input counterparts. The type attributes matter:

<form action="/" method="POST">
  <input type="text" name="name" id="name">
  <button>Submit</button>

  <!-- If you want to be more explicit... -->
  <button type="submit">Submit</button>

  <!-- ...or clear the form inputs back to their initial values -->
  <button type="reset">Reset</button>

  <!-- This prevents a `submit` action from firing which may be useful sometimes inside a form -->
  <button type="button">Non-submitting button</button>
</form>

Speaking of forms, buttons have some neat tricks up their sleeve where they can override attributes of the <form> itself.

<form action="/" method="get">

  <!-- override the action -->
  <button formaction="/elsewhere/" type="submit">Submit to elsewhere</button>

  <!-- override encytype -->
  <button formenctype="multipart/form-data" type="submit"></button>

  <!-- override method -->
  <button formmethod="post" type="submit"></button>

  <!-- do not validate fields -->
  <button formnovalidate type="submit"></button>

  <!-- override target e.g. open in new tab -->
  <button formtarget="_blank" type="submit"></button>

</form>

Autofocus

Since buttons are focusable elements, we can automatically focus on them when the page loads using the autofocus attribute:

<div class="modal">

  <h2>Save document?</h2>

  <button>Cancel</button>
  <button autofocus>OK</button>
</div>

Perhaps you'd do that inside of a modal dialog where one of the actions is a default action and it helps the UX (e.g. you can press Enter to dismiss the modal). Autofocusing after a user action is perhaps the only good practice here, moving a user's focus without their permission, as the autofocus attribute is capable of, can be a problem for screen reader and screen magnifier users.

Note thatautofocus may not work if the element is within an <iframe sandbox> for security reasons.

Disabling buttons

To prevent a button from being interactive, there is a disabled attribute you can use:

<button disabled>Pay Now</button>
<p class="error-message">Correct the form above to submit payment.</p>

Note that we've included descriptive text alongside the disabled button. It can be very frustrating to find a disabled button and not know why it's disabled. A better way to do this could be to let someone submit the form, and then explain why it didn't work in the validation feedback messaging.

Regardless, you could style a disabled button this way:

/* Might be good styles for ANY disabled element! */
button[disabled] {
  opacity: 0.5;
  pointer-events: none;
} 

We'll cover other states and styling later in this guide.

Buttons can contain child elements

A submit button and a submit input (<input type="submit">) are identical in functionality, but different in the sense that an input is unable to contain child elements while a button can.

<button>
   <svg aria-hidden="true" focusable="false">
     <path d="..." />
   </svg>
   <span class="callout">Big</span>
   Sale!
</button>

<button type="button">
  <span role="img" aria-label="Fox">
    🦊
  </span>
  Button
</button>

Note the focusable="false" attribute on the SVG element above. In that case, since the icon is decorative, this will help assistive technology only announce the button's label.

Styling and CSS considerations

Buttons are generally styled to look very button-like. They should look pressable. If you're looking for inspiration on fancy button styles, you'd do well looking at the CodePen Topic on Buttons.

1, 2, 3, 4, 5, 6

Cross-browser/platform button styles

How buttons look by default varies by browser and platform.

Just on macOS: Chrome, Safari, and Firefox (they look the same)
Add border: 0; to those same buttons as above, and we have different styles entirely.

While there is some UX truth to leaving the defaults of form elements alone so that they match that browser/platform's style and you get some affordance for free, designers typically don't like default styles, particularly ones that differ across browsers.

Resetting the default button style

Removing all the styles from a button is easier than you think. You'd think, as a form control, appearance: none; would help, but don't count on that. Actually all: revert; is a better bet to wipe the slate clean.

You can see how a variety of properties are involved

And that's not all of them. Here's a consolidated chunk of what Normalize does to buttons.

button {
  font-family: inherit; /* For all browsers */
  font-size: 100%; /* For all browsers */
  line-height: 1.15; /* For all browsers */
  margin: 0; /* Firefox and Safari have margin */
  overflow: visible; /* Edge hides overflow */
  text-transform: none; /* Firefox inherits text-transform */
  -webkit-appearance: button; /* Safari otherwise prevents some styles */
}

button::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

button:-moz-focusring {
  outline: 1px dotted ButtonText;
}

A consistent .button class

In addition to using reset or baseline CSS, you may want to have a class for buttons that gives you a strong foundation for styling and works across both links and buttons.

.button {
  border: 0;
  border-radius: 0.25rem;
  background: #1E88E5;
  color: white;
  font-family: -system-ui, sans-serif;
  font-size: 1rem;
  line-height: 1.2;
  white-space: nowrap;
  text-decoration: none;
  padding: 0.25rem 0.5rem;
  margin: 0.25rem;
  cursor: pointer;
}

Check out this Pen to see why all these properties are needed to make sure it works correctly across elements.

Button states

Just as with links, you'll want to style the states of buttons.

button:hover { }
button:focus { }
button:active { }
button:visited { } /* Maybe less so */

You may also want to use ARIA attributes for styling, which is a neat way to encourage using them correctly:

button[aria-pressed="true"] { }
button[aria-pressed="false"] { }

Link-styled buttons

There are always exceptions. For example, a website in which you need a button-triggered action within a sentence:

<p>You may open your <button>user settings</button> to change this.</p>

We've used a button instead of an anchor tag in the above code, as this hypothetical website opens user settings in a modal dialog rather than linking to another page. In this situation, you may want to style the button as if it looks like a link.

This is probably rare enough that you would probably make a class (e.g. .link-looking-button) that incorporates the reset styles from above and otherwise matches what you do for anchor links.

Breakout buttons

Remember earlier when we talked about the possibility of wrapping entire elements in links? If you have a button within another element, but you want that entire outer element to be clickable/tappable as if it's the button, that's a "breakout" button. You can use an absolutely-positioned pseudo-element on the button to expand the clickable area to the whole region. Fancy!

JavaScript considerations

Even without JavaScript, button elements can be triggered by the Space and Enter keys on a keyboard. That's part of what makes them such appealing and useful elements: they are discoverable, focusable, and interactive with assistive technology in a predictable way.

Perhaps any <button> in that situation should be inserted into the DOM by JavaScript. A tall order! Food for thought. 🤔

"Once" handlers

Say a button does something pretty darn important, like submitting a payment. It would be pretty scary if it was programmed such that clicking the button multiple times submitted multiple payment requests. It is situations like this where you would attach a click handler to a button that only runs once. To make that clear to the user, we'll disable the button on click as well.

document.querySelector("button").addEventListener('click', function(event) {
  event.currentTarget.setAttribute("disabled", true);
}, {
    once: true
});

Then you would intentionally un-disable the button and reattach the handler when necessary.

Inline handlers

JavaScript can be executed by activating a button through code on the button itself:

<button onclick="console.log('clicked');">
  Log it.
</button>

<button onmousedown="">
</button>

<button onmouseup="">
</button>

That practice went from being standard practice to being a faux pas (not abstracting JavaScript functionality away from HTML) to, eh, you need it when you need it. One advantage is that if you're injecting this HTML into the DOM, you don't need to bind/re-bind JavaScript event handlers to it because it already has one.

JavaScript frameworks

It's common in any JavaScript framework to make a component for handling buttons, as buttons typically have lots of variations. Those variations can be turned into an API of sorts. For example, in React:

const Button = ({ className, children }) => {
  const [activated, setActivated] = React.useState(false);
  return (
    <button
      className={`button ${className}`}
      aria-pressed={activated ? "true" : "false")
      onClick={() => setActivated(!activated)}
    >
      {children}
    </button>
  );
};

In that example, the <Button /> component ensures the button will have a button class and handles a toggle-like active class.

Accessibility considerations

The biggest accessibility consideration with buttons is actually using buttons. Don't try to replicate a button with a <div> or a <span>, which is, unfortunately, more common than you might think. It's very likely that will cause problems. (Did you deal with focusability? Did you deal with keyboard events? Great. There's still probably more stuff you're forgetting.)

Focus styles

Like all focusable elements, browsers apply a default focus style, which is usually a blue outline.

Focus styles on Chrome/macOS

While it's arguable that you should leave that alone as it's a very clear and obvious style for people that benefit from focus styles, it's also not out of the question to change it.

What you should not do is button:focus { outline: 0; } to remove it. If you ever remove a focus style like that, put it back at the same time.

button:focus {
  outline: 0; /* Removes the default blue ring */

  /* Now, let's create our own focus style */
  border-radius: 3px;
  box-shadow: 0 0 0 2px red;
}
Custom focus style

The fact that a button may become focused when clicked and apply that style at the same time is offputting to some. There is a trick (that has limited, but increasing, browser support) on removing focus styles from clicks and not keyboard events:

:focus:not(:focus-visible) { 
  outline: 0; 
}

ARIA

Buttons already have the role they need (role="button"). But there are some other ARIA attributes that are related to buttons:

  • aria-pressed: Turns a button into a toggle, between aria-pressed="true" and aria-pressed="false". More on button toggles, which can also be done with role="switch" and aria-checked="true".
  • aria-expanded: If the button controls the open/closed state of another element (like a dropdown menu), you apply this attribute to indicate that like aria-expanded="true".
  • aria-label: Overrides the text within the button. This is useful for labeling buttons that otherwise don't have text, but you're still probably better off using a visually-hidden class so it can be translated.
  • aria-labelledby: Points to an element that will act as the label for the button.

For that last one:

<button aria-labelledby="buttonText">
  Time is running out! 
  <span id="buttonText">Add to Cart</span>
</button>

Deque has a deeper dive blog post into button accessibility that includes much about ARIA.

Dialogs

If a button opens a dialog, your job is to move the focus inside and trap it there. When closing the dialog, you need to return focus back to that button so the user is back exactly where they started. This makes the experience of using a modal the same for someone who relies on assistive technology as for someone who doesn't.

Focus management isn't just for dialogs, either. If clicking a button runs a calculation and changes a value on the page, there is no context change there, meaning focus should remain on the button. If the button does something like "move to next page," the focus should be moved to the start of that next page.

Size

Don't make buttons too small. That goes for links and any sort of interactive control. People with any sort of reduced dexterity will benefit.

The classic Apple guideline for the minimum size for a touch target (button) is 44x44pt.

Here's some guidelines from other companies. Fitt's Law tells us smaller targets have greater error rates. Google even takes button sizes into consideration when evaluating the SEO of a site.

In addition to ample size, don't place buttons too close each other, whether they're stacked vertically or together on the same line. Give them some margin because people experiencing motor control issues run the risk of clicking the wrong one.

Activating buttons

Buttons work by being clicked/touched, pressing the Enter key, or pressing the Space key (when focused). Even if you add role="button" to a link or div, you won't get the spacebar functionality, so at the risk of beating a dead horse, use <button> in those cases.

The post A Complete Guide to Links and Buttons appeared first on CSS-Tricks.

Breakout Buttons

Andy covers a technique where a semantic <button> is used within a card component, but really, the whole card is clickable. The trick is to put a pseudo-element that goes beyond the button, covering the entire card. The tradeoff is that the pseudo-element sits on top of the text, so text selection is hampered a bit. I believe this is better than making the whole dang area a <button> because that would sacrifice semantics and likely cause extreme weirdness for assistive technology.

See the Pen
Semantic, progressively enhanced “break-out” button
by Andy Bell (@andybelldesign)
on CodePen.

You could do the same thing if your situation requires an <a> link instead of a <button>, but if that's the case, you actually can wrap the whole area in the link without much grief then wrap the part that appears to be a button in a span or something to make it look like a button.

This reminds me of the nested link problem: a large linked block that contains other different linked areas in it. Definitely can't nest anchor links. Sara Soueidan had the best answer where the "covering" link is placed within the card and absolutely positioned to cover the area while other links inside could be be layered on top with z-index.

I've moved her solution to a Pen for reference:

See the Pen
Nested Links Solution
by Chris Coyier (@chriscoyier)
on CodePen.

The post Breakout Buttons appeared first on CSS-Tricks.

Enhancing The Clickable Area Size

Here’s a great post by Ahmad Shadeed on making sure that clickable areas in our interfaces are, well, clickable. He writes about making sure that links, buttons and other elements meet accessibility standards for both touch and mouse, too.

I particularly like the section where Ahmad writes about making a fake circle around an element and where he shows this example:

On a similar note, Andy Bell just wrote up a few notes about creating a semantic “breakout” button to make an entire element clickable. This is a common pattern where you might want a design element that looks like a card but the whole thing is effectively a single button. There are a few accessibility tips that Andy brings up that are very much worth taking a look at!

The same sort of thing might be said about buttons. Using a rounded border-radius trims the corners, creating small un-clickable areas.

Direct Link to ArticlePermalink

The post Enhancing The Clickable Area Size appeared first on CSS-Tricks.

Weekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled Exchanges

In this week's roundup, the string length of two emojis is not always equal, something to consider before making that rounded button, and we may have a new way to share web apps between devices, even when they are offline.

The JavaScript string length of emoji characters

A single rendered emoji can have a JavaScript string length of up to 7 if it contains additional Unicode scalar values that represent a skin tone modifier, gender specification, and multicolor rendering.

(via Henri Sivonen)

An accessibility issue with rounded buttons

Be aware that applying CSS border-radius to a <button> element reduces the button’s interactive area (“those lost corner pixels are no longer clickable”).

You can avoid this accessibility issue in CSS, e.g., by emulating rounded corners via border-image instead, or by overlaying the button with an absolutely positioned, transparent ::before pseudo-element.

(via Tyler Sticka)

Sharing web pages while offline with Bundled Exchanges

Chrome plans to add support for navigation to Bundled Exchanges (part of Web Packaging). A bundled exchangeis a collection of HTTP request/response pairs, and it can be used to bundle a web page and all of its resources.

The browser should be able to parse and verify the bundle’s signature and then navigate to the website represented by the bundle without actually connecting to the site as all the necessary subresources could be served by the bundle.

Kinuko Yasuda from Google has posted a video that demonstrates how Bundled Exchanges enable sharing web pages (e.g., a web game) with other devices while offline.

(via Kinuko Yasuda)


Read even more news in my weekly Sunday issue, which can be delivered to you via email every Monday morning. Visit webplatform.news for more information.

The post Weekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled Exchanges appeared first on CSS-Tricks.

Ghost Buttons with Directional Awareness in CSS

It would surprise me if you'd never come across a ghost button 👻. You know the ones: they have a transparent background that fills with a solid color on hover. Smashing Magazine has a whole article going into the idea. In this article, we’re going to build a ghost button, but that will be the easy part. The fun and tricky part will be animating the fill of that ghost button such that the background fills up in the direction from which a cursor hovers over it.

Here’s a basic starter for a ghost button:

See the Pen
Basic Ghost Button 👻
by Jhey (@jh3y)
on CodePen.

In most cases, the background-color has a transition to a solid color. There are designs out there where the button might fill from left to right, top to bottom, etc., for some visual flair. For example, here’s left-to-right:

See the Pen
Directional filling Ghost Button 👻
by Jhey (@jh3y)
on CodePen.

There's a UX nitpick here. It feels off if you hover against the fill. Consider this example. The button fills from the left while you hover from the right.

Hover feels off 👎

It is better if the button fills from our initial hover point.

Hover feels good 👍

So, how can we give the button directional awareness? Your initial instinct might be to reach for a JavaScript solution, but we can create something with CSS and a little extra markup instead.

For those in camp TL;DR, here are some pure CSS ghost buttons with directional awareness!

See the Pen
Pure CSS Ghost Buttons w/ Directional Awareness ✨👻😎
by Jhey (@jh3y)
on CodePen.

Let’s build this thing step by step. All the code is available in this CodePen collection.

Creating a foundation

Let’s start by creating the foundations of our ghost button. The markup is straightforward.

<button>Boo!</button>

Our CSS implementation will leverage CSS custom properties. These make maintenance easier. They also make for simple customization via inline properties.

button {
  --borderWidth: 5;
  --boxShadowDepth: 8;
  --buttonColor: #f00;
  --fontSize: 3;
  --horizontalPadding: 16;
  --verticalPadding: 8;

  background: transparent;
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  box-shadow: calc(var(--boxShadowDepth) * 1px) calc(var(--boxShadowDepth) * 1px) 0 #888;
  color: var(--buttonColor);
  cursor: pointer;
  font-size: calc(var(--fontSize) * 1rem);
  font-weight: bold;
  outline: transparent;
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  transition: box-shadow 0.15s ease;
}

button:hover {
  box-shadow: calc(var(--boxShadowDepth) / 2 * 1px) calc(var(--boxShadowDepth) / 2 * 1px) 0 #888;
}

button:active {
  box-shadow: 0 0 0 #888;
}

Putting it all together gives us this:

See the Pen
Ghost Button Foundation 👻
by Jhey (@jh3y)
on CodePen.

Great! We have a button and a hover effect, but no fill to go with it. Let’s do that next.

Adding a fill

To do this, we create elements that show the filled state of our ghost button. The trick is to clip those elements with clip-path and hide them. We can reveal them when we hover over the button by transitioning the clip-path.

Child element with a 50% clip

They must line up with the parent button. Our CSS variables will help a lot here.

At first thought, we could have reached for pseudo-elements. There won't be enough pseudo-elements for every direction though. They will also interfere with accessibility... but more on this later.

Let's start by adding a basic fill from left to right on hover. First, let's add a div. That div will need the same text content as the button.

<button>Boo!
  <div>Boo!</div>
</button>

Now we need to line our div up with the button. Our CSS variables will do the heavy lifting here.

button div {
  background: var(--buttonColor);
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  bottom: calc(var(--borderWidth) * -1px);
  color: var(--bg, #fafafa);
  left: calc(var(--borderWidth) * -1px);
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
}

Finally, we clip the div out of view and add a rule that will reveal it on hover by updating the clip. Defining a transition will give it that cherry on top.

button div {
  --clip: inset(0 100% 0 0);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  transition: clip-path 0.25s ease, -webkit-clip-path 0.25s ease;
  // ...Remaining div styles
}

button:hover div {
  --clip: inset(0 0 0 0);
}

See the Pen
Ghost Button w/ LTR fill 👻
by Jhey (@jh3y)
on CodePen.

Adding directional awareness

So, how might we add directional awareness? We need four elements. Each element will be responsible for detecting a hover entry point. With clip-path, we can split the button area into four segments.

Four :hover segments

Let's add four spans to a button and position them to fill the button.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</button>
button span {
  background: var(--bg);
  bottom: calc(var(--borderWidth) * -1px);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  left: calc(var(--borderWidth) * -1px);
  opacity: 0.5;
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
  z-index: 1;
}

We can target each element and assign a clip and color with CSS variables.

button span:nth-of-type(1) {
  --bg: #00f;
  --clip: polygon(0 0, 100% 0, 50% 50%, 50% 50%);
}
button span:nth-of-type(2) {
  --bg: #f00;
  --clip: polygon(100% 0, 100% 100%, 50% 50%);
}
button span:nth-of-type(3) {
  --bg: #008000;
  --clip: polygon(0 100%, 100% 100%, 50% 50%);
}
button span:nth-of-type(4) {
  --bg: #800080;
  --clip: polygon(0 0, 0 100%, 50% 50%);
}

Cool. To test this, let's change the opacity on hover.

button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
  opacity: 1;
}
So close

Uh-oh. There's an issue here. If we enter and hover one segment but then hover over another, the fill direction would change. That's going to look off. To fix this, we can set a z-index and clip-path on hover so that a segment fills the space.

button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
  --clip: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  opacity: 1;
  z-index: 2;
}

See the Pen
Pure CSS Directional Awareness w/ clip-path 👻
by Jhey (@jh3y)
on CodePen.

Putting it all together

We know how to create the fill animation, and we know how to detect direction. How can we put the two together? Use the sibling combinator!

Doing so means when we hover a directional segment, we can reveal a particular fill element.

First, let's update the markup.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <div>Boo!</div>
  <div>Boo!</div>
  <div>Boo!</div>
  <div>Boo!</div>
</button>

Now, we can update the CSS. Referring to our left-to-right fill, we can reuse the div styling. We only need to set a specific clip-path for each div. I've approached the ordering the same as some property values. The first child is top, the second is right, and so on.

button div:nth-of-type(1) {
  --clip: inset(0 0 100% 0);
}
button div:nth-of-type(2) {
  --clip: inset(0 0 0 100%);
}
button div:nth-of-type(3) {
  --clip: inset(100% 0 0 0);
}
button div:nth-of-type(4) {
  --clip: inset(0 100% 0 0);
}

The last piece is to update the clip-path for the relevant div when hovering the paired segment.

button span:nth-of-type(1):hover ~ div:nth-of-type(1),
button span:nth-of-type(2):hover ~ div:nth-of-type(2),
button span:nth-of-type(3):hover ~ div:nth-of-type(3),
button span:nth-of-type(4):hover ~ div:nth-of-type(4) {
  --clip: inset(0 0 0 0);
}

Tada! We have a pure CSS ghost button with directional awareness.

See the Pen
Pure CSS Ghost Button w/ Directional Awareness 👻
by Jhey (@jh3y)
on CodePen.

Accessibility

In its current state, the button isn't accessible.

The extra markup is read by VoiceOver.

Those extra elements aren't helping much as a screen reader will repeat the content four times. We need to hide those elements from a screen reader.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <div aria-hidden="true">Boo!</div>
  <div aria-hidden="true">Boo!</div>
  <div aria-hidden="true">Boo!</div>
  <div aria-hidden="true">Boo!</div>
</button>

No more repeated content.

See the Pen
Accessible Pure CSS Ghost Button w/ Directional Awareness 👻
by Jhey (@jh3y)
on CodePen.

That’s it!

With a little extra markup and some CSS trickery, we can create ghost buttons with directional awareness. Use a preprocessor or put together a component in your app and you won't need to write out all the HTML, too.

Here's a demo making use of inline CSS variables to control the button color.

See the Pen
Pure CSS Ghost Buttons w/ Directional Awareness ✨👻😎
by Jhey (@jh3y)
on CodePen.

The post Ghost Buttons with Directional Awareness in CSS appeared first on CSS-Tricks.

Recreating Netlify’s Neat-o Sliding Button Effect

Have you seen Netlify's press page? It's one of those places where you can snag a download of the company's logo. I was looking for it this morning because I needed the logo to use as a featured image for a post here on CSS-Tricks.

Well, I noticed they have these pretty looking buttons to download the logo. They're small and sharp. They grab attention but aren't in the way.

They're also interactive! Look at the way they expand and reveal the word "Download" on hover.

Nice, right?! I actually noticed that they looked a little off in Safari.

That made me curious about how they're made. So, I recreated them here as a demo while cleaning up some of the spacing stuff:

See the Pen
Netlify Sliding Buttons
by Geoff Graham (@geoffgraham)
on CodePen.

How'd they do it? The recipe really comes down to four ingredients:

  • Using the left property to slide the "Download" label in and out of view
  • Using padding on the button's hover state to create additional room for showing the "Download" label on hover
  • Declaring a 1:1 scale() on the button's hover state so all the content stays contained when things move around.
  • Specifiying a transition on the button's padding, the background-position of the button icon and the transform property to make for a smooth animation between the button's default and hover states.

Here's what that looks like without all the presentation styles:

See the Pen
Style-less Netlify Sliding Buttons
by Geoff Graham (@geoffgraham)
on CodePen.

If you're having a tough time visualizing what's happening, here's an illustration showing how the "Download" label is hidden outside of the button (thanks to overflow: hidden) and where it's pushed into view on hover.

So, by putting negative left values on the icon and the "Download" label, we're pushing them out of view and then resetting those to positive values when the entire button is hovered.

/* Natural State */
.button {
    background: 
      #f6bc00 
      url(data:image/svg+xml;base64,...)
      no-repeat -12px center;
    overflow: hidden;
}
  
.button span:nth-child(1) {
  position: absolute;
  left: -70px;
}

/* Hovered State */
.button:hover {
  padding-left: 95px;
  background-position: 5px center;
}

.button span:nth-child(1) {
  position: absolute;
  left: -70px;
}

Notice that leaving things in this state would let the button icon slide into view and create enough room for the "Download" label, but the label would actually float off the button on hover.

See the Pen
Style-less Netlify Sliding Buttons
by Geoff Graham (@geoffgraham)
on CodePen.

That's where adding a 1:1 scale on the button helps keep things in tact.

* Hovered State */
.button:hover {
  padding-left: 95px;
  background-position: 5px center;
  transform: scale(1, 1);
}

Those padding values are magic numbers. They'll be different for you based on the font, font-size, and other factors, so your mileage may vary.

The last core ingredient is the transition property, which makes everything slide smoothly into place rather than letting them snap. It's provides a much nicer experience.

/* Natural State */
.button {
    background: 
      #f6bc00 
      url(data:image/svg+xml;base64,...)
      no-repeat -12px center;
    overflow: hidden;
    transition: padding .2s ease, background-position .2s ease, transform .5s ease;
}

Toss in some little flourishes, like rounded corners and such, and you've got a pretty slick button.

See the Pen
Netlify Sliding Buttons
by Geoff Graham (@geoffgraham)
on CodePen.

The post Recreating Netlify’s Neat-o Sliding Button Effect appeared first on CSS-Tricks.

Nested Gradients with background-clip

I can't say I use background-clip all that often. I'd wager it's hardly ever used in day-to-day CSS work. But I was reminded of it in a post by Stefan Judis, which coincidentally was itself a learning-response post to a post over here by Ana Tudor.

Here's a quick explanation.

You've probably seen this thing a million times:

The box model visualizer in DevTools.

That's showing you the size and position of an element, as well as how that size is made up: content size, padding, margin, and border.

Those things aren't just theoretical to help with understanding and debugging. Elements actually have a content-box, padding-box, and border-box. Perhaps we encounter that most often when we literally set the box-sizing property. (It's tremendously useful to universally set it to border-box).

Those values are the same values as background-clip uses! Meaning that you can set a background to only cover those specific areas. And because multiple backgrounds is a thing, that means we can have multiple backgrounds with different clipping on each.

Like this:

See the Pen
Multiple background-clip
by Chris Coyier (@chriscoyier)
on CodePen.

But that's boring and there are many ways to pull off that effect, like using borders, outline, and box-shadow or any combination of them.

What is more interesting is the fact that those backgrounds could be gradients, and that's a lot harder to pull off any other way!

See the Pen
Nested Gradients
by Chris Coyier (@chriscoyier)
on CodePen.

The post Nested Gradients with background-clip appeared first on CSS-Tricks.

The ineffectiveness of lonely icons

Icons are great and all, but as we've been shown time and time again, they often don't do the job all by themselves. Even if you do a good job with the accessibility part and make sure there is accompanying text there for assistive technology, in an ironic twist, you might be confusing people who browse visually, like the situation Matt Wilcox describes with his mother in this linked article.

I'm a fan of this markup pattern, including the inline SVG as the preferred icon system:

<button>
  <svg class="icon icon-cart" viewBox="0 0 100 100" aria-hidden="true">
    <!-- all your hot svg action, like: -->
    <path d=" ... " />
  </svg>
  Add to Cart
</button>

Or, if the button is really a link and not a JavaScript-powered action, I'll use an <a href=""> instead of a <button> wrapper.

Direct Link to ArticlePermalink

The post The ineffectiveness of lonely icons appeared first on CSS-Tricks.